@geminixiang/mikan 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/CHANGELOG.md +20 -0
- package/README.md +21 -116
- package/dist/cli/onboard.d.ts.map +1 -1
- package/dist/cli/onboard.js +25 -11
- package/dist/cli/onboard.js.map +1 -1
- package/dist/cli/pm2-ecosystem.d.ts +5 -0
- package/dist/cli/pm2-ecosystem.d.ts.map +1 -0
- package/dist/cli/pm2-ecosystem.js +58 -0
- package/dist/cli/pm2-ecosystem.js.map +1 -0
- package/dist/sandbox/container.d.ts +1 -0
- package/dist/sandbox/container.d.ts.map +1 -1
- package/dist/sandbox/container.js +33 -3
- package/dist/sandbox/container.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,26 @@ change in a minor release.
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [1.0.1] - 2026-10-07
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- `mikan onboard` asks for a sandbox image instead of a sandbox mode: the published
|
|
19
|
+
`mikan-sandbox` image (recommended), `debian:trixie-slim`, or any other image. It writes
|
|
20
|
+
`~/.mikan/ecosystem.config.cjs` with that image and prints the `docker pull` and
|
|
21
|
+
`pm2 start` commands, so the PM2 file no longer has to be downloaded. An existing
|
|
22
|
+
ecosystem file is kept. `host` and `container:<name>` still work through `--sandbox` but
|
|
23
|
+
are no longer offered.
|
|
24
|
+
- The README and the quick start and deployment guides, in all four languages, follow the
|
|
25
|
+
onboard flow: install, `mikan onboard`, `docker pull`, and `pm2 start` with the generated
|
|
26
|
+
ecosystem file. The docs site has a new logo, favicon, hero image, and footer.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- Stopping a run, or a command timing out, now ends the command inside the container
|
|
31
|
+
sandbox. Before, only the host `docker exec` client was killed and the command kept
|
|
32
|
+
running in the container. Images without `setsid` keep the old behavior.
|
|
33
|
+
|
|
14
34
|
## [1.0.0] - 2026-10-06
|
|
15
35
|
|
|
16
36
|
1.0.0 is the first stable release. It replaces the 0.5.3 runtime, which ran on
|
package/README.md
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="src/content/docs/assets/mikan-
|
|
2
|
+
<img src="src/content/docs/assets/mikan-hero.png" alt="mikan — your team's AI coding agent, in chat" width="100%">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
# @geminixiang/mikan
|
|
5
|
+
# <img src="src/content/docs/assets/mikan-logo.png" alt="" width="44" align="top"> @geminixiang/mikan
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@geminixiang/mikan)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
|
|
10
|
-
Self-hosted AI coding agent
|
|
10
|
+
Self-hosted AI coding agent that works in your team's chat.
|
|
11
|
+
|
|
12
|
+
- **One office per conversation** — every Slack channel and DM gets its own workspace, sandbox, credentials, and memory; each thread runs as its own session.
|
|
13
|
+
- **Where your team already talks** — Slack, Telegram, Discord, and GitHub (experimental).
|
|
14
|
+
- **Real tools, contained** — in `image:` mode, commands run in a per-office Docker sandbox with credentials from that office's vault.
|
|
11
15
|
|
|
12
16
|
Upgrading from 0.5.3? Follow [Upgrading from 0.5.3](src/content/docs/deployment.mdx#upgrading-from-053); `mikan migrate` converts the state in place.
|
|
13
17
|
|
|
@@ -21,8 +25,8 @@ mikan keeps the chat record, agent session, and execution runtime separate:
|
|
|
21
25
|
|
|
22
26
|
- **Conversation office** is the unit everything else hangs off: one conversation's working area plus its own sandbox runtime. An office is identified by its platform and raw conversation id, and its directory is named by an office key (`v1-<platform>-<readable-id>-<hash>`), so two platforms can never collide on the same raw id.
|
|
23
27
|
- **Chat / conversation data** is the platform-facing record: `log.jsonl`, attachments, and conversation files.
|
|
24
|
-
- **Session orchestration** turns platform events into agent runs, handles top-level/thread scopes, and persists
|
|
25
|
-
- **mikan agent harness** (`src/harness/`, built on pi-
|
|
28
|
+
- **Session orchestration** turns platform events into agent runs, handles top-level/thread scopes, and persists agent sessions in one host-only SQLite `sessions.db` per office under the State dir.
|
|
29
|
+
- **mikan agent harness** (`src/harness/`, built on pi-durable and pi-ai) owns run preparation, authorized prompts/tools, response presentation, and native Pi session integration. Pi handles the model/tool loop, persistence, compaction, retries, and cancellation.
|
|
26
30
|
- **Sandbox runtime** is where tool commands execute: host, or a Docker container or image.
|
|
27
31
|
- **Vault** provides runtime credentials as env vars and mounted secret files.
|
|
28
32
|
|
|
@@ -31,7 +35,7 @@ mikan keeps the chat record, agent session, and execution runtime separate:
|
|
|
31
35
|
- **Multi-platform** — Slack, Telegram, Discord, and GitHub (experimental) adapters
|
|
32
36
|
- **Concurrent conversations** — Slack threads, Discord replies/threads, and Telegram reply chains run as independent sessions
|
|
33
37
|
- **Conversation offices** — one office directory and one sandbox runtime per conversation, with public/private visibility derived from the platform conversation type
|
|
34
|
-
- **Sandbox execution** —
|
|
38
|
+
- **Sandbox execution** — one managed Docker container per conversation office
|
|
35
39
|
- **Credential vaults** — `/login` stores credentials under `~/.mikan` and injects env into sandbox runs
|
|
36
40
|
- **Web session viewer** — read-only web view of the current session via `session` / `/session`
|
|
37
41
|
- **Persistent memory** — workspace-level and per-office `MEMORY.md`
|
|
@@ -39,72 +43,22 @@ mikan keeps the chat record, agent session, and execution runtime separate:
|
|
|
39
43
|
- **Events** — the agent schedules one-shot or recurring runs with its `event` tool
|
|
40
44
|
- **Multi-provider** — any provider/model supported by `pi-ai`
|
|
41
45
|
|
|
42
|
-
## Requirements
|
|
43
|
-
|
|
44
|
-
- Node.js >= 24.15.0
|
|
45
|
-
|
|
46
|
-
## Installation
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
npm install -g @geminixiang/mikan
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Or from source:
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
npm install && npm run build
|
|
56
|
-
```
|
|
57
|
-
|
|
58
46
|
## Quick Start
|
|
59
47
|
|
|
60
|
-
|
|
48
|
+
You need Node.js 24.15 or later and Docker.
|
|
61
49
|
|
|
62
50
|
```bash
|
|
63
51
|
npm i -g @geminixiang/mikan pm2
|
|
64
|
-
|
|
65
|
-
# One-time setup: platform tokens, LLM provider, and sandbox mode,
|
|
66
|
-
# written to ~/.mikan/settings.json and ~/.mikan/mikan.env (0600)
|
|
67
|
-
mikan onboard
|
|
68
|
-
|
|
69
|
-
# Pull the sandbox image the default deployment runs tools in
|
|
52
|
+
mikan onboard # pick a chat platform, a model, and a sandbox image
|
|
70
53
|
docker pull ghcr.io/geminixiang/mikan-sandbox:latest
|
|
71
|
-
|
|
72
|
-
# Grab the maintained ecosystem file (supervision only)
|
|
73
|
-
curl -O https://raw.githubusercontent.com/geminixiang/mikan/main/deploy/pm2/ecosystem.config.cjs
|
|
74
|
-
|
|
75
|
-
pm2 start ecosystem.config.cjs
|
|
76
|
-
pm2 save
|
|
77
|
-
pm2 startup # run the printed command to enable boot autostart
|
|
54
|
+
pm2 start ~/.mikan/ecosystem.config.cjs && pm2 save
|
|
78
55
|
```
|
|
79
56
|
|
|
80
|
-
|
|
57
|
+
That's it: `@mention` the bot in a channel or send it a DM. `mikan onboard` prints the exact `docker pull` line for the image you picked; the recommended image is about 900 MB, so the pull takes a few minutes the first time. Run `pm2 startup` once if mikan should start on boot.
|
|
81
58
|
|
|
82
|
-
|
|
83
|
-
args: "--sandbox=image:ghcr.io/geminixiang/mikan-sandbox:latest",
|
|
84
|
-
```
|
|
59
|
+
Everything lives in `~/.mikan`: `settings.json` for behavior (model, sandbox limits, reply modes), `mikan.env` for secrets and platform tokens, and `ecosystem.config.cjs` for process supervision. To add another platform later, put its tokens in `mikan.env` and run `pm2 reload ~/.mikan/ecosystem.config.cjs` so pm2 rereads the file; `mikan env` lists every variable mikan reads.
|
|
85
60
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
SLACK_APP_TOKEN=xapp-...
|
|
90
|
-
SLACK_BOT_TOKEN=xoxb-...
|
|
91
|
-
TELEGRAM_BOT_TOKEN=123456:ABC-...
|
|
92
|
-
DISCORD_BOT_TOKEN=MTI...
|
|
93
|
-
GITHUB_AGENT_TOKEN=github_pat_...
|
|
94
|
-
GITHUB_WEBHOOK_SECRET=...
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
GitHub also needs the repositories it answers in, as `"github": { "repos": ["acme/*"] }` in `~/.mikan/settings.json`.
|
|
98
|
-
|
|
99
|
-
Tail logs with `pm2 logs mikan`; upgrade with `npm i -g @geminixiang/mikan`, check `mikan migrate --sandbox=<mode> --dry-run` for pending State migrations, then `pm2 reload mikan`. See [the deployment guide](src/content/docs/deployment.mdx) for sandbox images, graceful shutdown, and the health endpoint.
|
|
100
|
-
|
|
101
|
-
For a one-off foreground run, the same CLI works directly:
|
|
102
|
-
|
|
103
|
-
```bash
|
|
104
|
-
mikan [--sandbox=<mode>] [<working-directory>]
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
The working directory is optional: it defaults to `~/.mikan/workspace` and is created on first run.
|
|
61
|
+
Upgrade with `npm i -g @geminixiang/mikan`, check `mikan migrate --sandbox=image:<image> --dry-run` for pending State migrations, then `pm2 reload mikan`. See [the deployment guide](src/content/docs/deployment.mdx) for graceful shutdown and the health endpoint.
|
|
108
62
|
|
|
109
63
|
## Platforms
|
|
110
64
|
|
|
@@ -117,15 +71,11 @@ Slack threads, Discord replies/threads, and Telegram reply chains are mapped to
|
|
|
117
71
|
|
|
118
72
|
## Sandbox
|
|
119
73
|
|
|
120
|
-
|
|
121
|
-
| ------------------ | ----------------------------------------------------------------------------- |
|
|
122
|
-
| `host` (default) | Run on host; no vault env injection |
|
|
123
|
-
| `container:<name>` | Run in an existing shared container; everyone sharing it shares its one vault |
|
|
124
|
-
| `image:<image>` | Auto-provision one Docker container and one vault per conversation office |
|
|
74
|
+
Use `image:<image>`: mikan starts one Docker container and one credential vault per conversation office, and enforces office visibility. `mikan onboard` sets it up.
|
|
125
75
|
|
|
126
|
-
|
|
76
|
+
`host` (run directly on the machine) and `container:<name>` (one shared, existing container) still work but are not recommended: they give the agent the whole machine or one shared filesystem, skip `/login` credential injection, and do not enforce office visibility.
|
|
127
77
|
|
|
128
|
-
|
|
78
|
+
Each office is **public** or **private**, following the Slack conversation type: public channels are public — every other office can read them (read-only, under `/workspace/public/`) and they may write the shared `MEMORY.md` and `skills/`. Private channels, DMs, group DMs, and externally shared channels are private — visible only to themselves, reading shared knowledge and public offices without writing back. Unknown conversation kinds are private. The admin portal or `/pi-sandbox visibility private` can narrow a public channel; nothing can widen beyond Slack. Visibility governs data access only; execution isolation is unaffected.
|
|
129
79
|
|
|
130
80
|
For routing, mounts, vault behavior, and managed container details, see [src/content/docs/sandbox.mdx](src/content/docs/sandbox.mdx).
|
|
131
81
|
|
|
@@ -144,55 +94,9 @@ For routing, mounts, vault behavior, and managed container details, see [src/con
|
|
|
144
94
|
|
|
145
95
|
`session` is the only command accepted without a leading slash. See [src/content/docs/commands.mdx](src/content/docs/commands.mdx) for the full command reference and web session viewer setup.
|
|
146
96
|
|
|
147
|
-
## Configuration
|
|
148
|
-
|
|
149
|
-
mikan reads global settings from `~/.mikan/settings.json`; host-only per-conversation overrides live at `~/.mikan/conversations/<office-key>/settings.json`. Legacy workspace settings are migrated once, then ignored.
|
|
150
|
-
|
|
151
|
-
```json
|
|
152
|
-
{
|
|
153
|
-
"llm": {
|
|
154
|
-
"provider": "anthropic",
|
|
155
|
-
"model": "claude-sonnet-4-6",
|
|
156
|
-
"thinkingLevel": "off"
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
See [src/content/docs/configuration.md](src/content/docs/configuration.md) for all fields.
|
|
162
|
-
|
|
163
|
-
## Data layout
|
|
164
|
-
|
|
165
|
-
```text
|
|
166
|
-
~/.mikan/
|
|
167
|
-
├── settings.json
|
|
168
|
-
├── mikan.env
|
|
169
|
-
├── models.json
|
|
170
|
-
├── migrations.json
|
|
171
|
-
├── office-registry.json
|
|
172
|
-
├── conversations/
|
|
173
|
-
│ └── <office-key>/
|
|
174
|
-
│ ├── settings.json
|
|
175
|
-
│ ├── sessions.db
|
|
176
|
-
│ └── events/
|
|
177
|
-
└── vaults/
|
|
178
|
-
|
|
179
|
-
<working-directory>/
|
|
180
|
-
├── MEMORY.md
|
|
181
|
-
├── skills/
|
|
182
|
-
├── agents/
|
|
183
|
-
└── <office-key>/
|
|
184
|
-
├── MEMORY.md
|
|
185
|
-
├── SYSTEM.md
|
|
186
|
-
├── log.jsonl
|
|
187
|
-
├── attachments/
|
|
188
|
-
├── scratch/
|
|
189
|
-
└── skills/
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
Office directories are named by office key (`v1-<platform>-<readable-id>-<hash>`) and are not reversible to a raw platform id, so `office-registry.json` records each office's `(platform, conversationId)`. `mikan office list` prints the registered offices. `mikan migrate` upgrades a 0.5.3 install to this layout; see [State migrations](src/content/docs/deployment.mdx).
|
|
193
|
-
|
|
194
97
|
## More docs
|
|
195
98
|
|
|
99
|
+
- [Configuration](src/content/docs/configuration.md)
|
|
196
100
|
- [Events](src/content/docs/events.md)
|
|
197
101
|
- [Skills](src/content/docs/skills.md)
|
|
198
102
|
- [Deployment](src/content/docs/deployment.mdx)
|
|
@@ -203,6 +107,7 @@ Office directories are named by office key (`v1-<platform>-<readable-id>-<hash>`
|
|
|
203
107
|
## Development
|
|
204
108
|
|
|
205
109
|
```bash
|
|
110
|
+
npm install && npm run build
|
|
206
111
|
npm run dev
|
|
207
112
|
npm test
|
|
208
113
|
npm run lint
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/cli/onboard.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/cli/onboard.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAkI5C,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAUhG;AAUD,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,SAAS,EACb,KAAK,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/B,OAAO,CAAC,MAAM,CAAC,CA2DjB;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBzE"}
|
package/dist/cli/onboard.js
CHANGED
|
@@ -5,6 +5,7 @@ import { ENV_MANIFEST, envSetReport, readEnv } from "../env-manifest.js";
|
|
|
5
5
|
import { createGlobalSettingsFile } from "../settings/index.js";
|
|
6
6
|
import { recordAllMigrations } from "../migrations/index.js";
|
|
7
7
|
import { atomicWritePrivateFile } from "../file-guards.js";
|
|
8
|
+
import { MINIMAL_SANDBOX_IMAGE, PM2_ECOSYSTEM_FILENAME, PUBLISHED_SANDBOX_IMAGE, renderPm2Ecosystem, } from "./pm2-ecosystem.js";
|
|
8
9
|
class OnboardAborted extends Error {
|
|
9
10
|
constructor() {
|
|
10
11
|
super("Onboarding aborted.");
|
|
@@ -92,13 +93,18 @@ async function askLlm(io, env) {
|
|
|
92
93
|
}, null, 2);
|
|
93
94
|
return { llm: { provider, model: ids[0] }, modelsJson };
|
|
94
95
|
}
|
|
95
|
-
async function
|
|
96
|
-
io.print("\nStep 3/3 — sandbox (
|
|
96
|
+
async function askSandboxImage(io) {
|
|
97
|
+
io.print("\nStep 3/3 — sandbox image (one Docker container per conversation; needs Docker)");
|
|
97
98
|
const index = await askChoice(io, [
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
`${PUBLISHED_SANDBOX_IMAGE} (recommended: git, gh, node, python, curl)`,
|
|
100
|
+
`${MINIMAL_SANDBOX_IMAGE} (minimal: shell only, no git or package installs)`,
|
|
101
|
+
"Another image from Docker Hub or a registry…",
|
|
102
|
+
], "Sandbox image");
|
|
103
|
+
if (index === 0)
|
|
104
|
+
return PUBLISHED_SANDBOX_IMAGE;
|
|
105
|
+
if (index === 1)
|
|
106
|
+
return MINIMAL_SANDBOX_IMAGE;
|
|
107
|
+
return askRequired(io, " Image (e.g. registry/name:tag): ");
|
|
102
108
|
}
|
|
103
109
|
export function renderEnvFile(existing, vars) {
|
|
104
110
|
const pending = new Map(Object.entries(vars));
|
|
@@ -132,13 +138,13 @@ export async function runOnboardWizard(stateDir, io, paths) {
|
|
|
132
138
|
const env = {};
|
|
133
139
|
const { githubRepos } = await askAdapter(io, env);
|
|
134
140
|
const { llm, modelsJson } = await askLlm(io, env);
|
|
135
|
-
const
|
|
141
|
+
const image = await askSandboxImage(io);
|
|
136
142
|
const modelsPath = join(stateDir, "models.json");
|
|
137
143
|
if (modelsJson && existsSync(modelsPath)) {
|
|
138
144
|
io.print(`models.json already exists at ${modelsPath}. Add the provider manually before retrying; nothing was written.`);
|
|
139
145
|
return 1;
|
|
140
146
|
}
|
|
141
|
-
io.print(`Review settings\nProvider: ${llm.provider}\nModel: ${llm.model}\nSandbox:
|
|
147
|
+
io.print(`Review settings\nProvider: ${llm.provider}\nModel: ${llm.model}\nSandbox: image:${image}\nSettings: ${settingsPath}\nCredentials: ${paths?.envFilePath ?? join(stateDir, "mikan.env")}${modelsJson ? `\nModels: ${modelsPath}` : ""}\nSecrets are hidden.`);
|
|
142
148
|
if (!(await io.confirm("Save these settings?"))) {
|
|
143
149
|
io.print("Onboarding cancelled; nothing was written.");
|
|
144
150
|
return 1;
|
|
@@ -155,10 +161,18 @@ export async function runOnboardWizard(stateDir, io, paths) {
|
|
|
155
161
|
atomicWritePrivateFile(envFilePath, renderEnvFile(existing, env));
|
|
156
162
|
io.print(`Wrote ${envFilePath} (0600)`);
|
|
157
163
|
io.print(`\n${envSetReport((name) => env[name] ?? readEnv(name))}`);
|
|
158
|
-
const
|
|
164
|
+
const ecosystemPath = join(stateDir, PM2_ECOSYSTEM_FILENAME);
|
|
165
|
+
if (existsSync(ecosystemPath)) {
|
|
166
|
+
io.print(`Kept existing ${ecosystemPath}`);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
atomicWritePrivateFile(ecosystemPath, renderPm2Ecosystem(image));
|
|
170
|
+
io.print(`Wrote ${ecosystemPath}`);
|
|
171
|
+
}
|
|
159
172
|
io.print("Next steps:");
|
|
160
|
-
io.print(`
|
|
161
|
-
io.print(`
|
|
173
|
+
io.print(` 1. docker pull ${image}`);
|
|
174
|
+
io.print(` 2. pm2 start ${ecosystemPath} && pm2 save`);
|
|
175
|
+
io.print(` Foreground instead: set -a; source ${envFilePath}; set +a; mikan --sandbox=image:${image}`);
|
|
162
176
|
return 0;
|
|
163
177
|
}
|
|
164
178
|
export async function runOnboardCommand(stateDir) {
|
package/dist/cli/onboard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onboard.js","sourceRoot":"","sources":["../../src/cli/onboard.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport * as prompts from \"@clack/prompts\";\nimport { ENV_MANIFEST, envSetReport, readEnv } from \"../env-manifest.js\";\nimport { createGlobalSettingsFile } from \"../settings/index.js\";\nimport { recordAllMigrations } from \"../migrations/index.js\";\nimport type { OnboardLlmChoice } from \"../types.js\";\nimport { atomicWritePrivateFile } from \"../file-guards.js\";\nimport type { OnboardIo } from \"./types.js\";\n\nclass OnboardAborted extends Error {\n constructor() {\n super(\"Onboarding aborted.\");\n }\n}\n\nasync function answer<T>(result: Promise<T>): Promise<Exclude<T, symbol>> {\n const value = await result;\n if (prompts.isCancel(value)) throw new OnboardAborted();\n return value as Exclude<T, symbol>;\n}\n\nfunction terminalIo(): OnboardIo {\n return {\n ask: (message) => answer(prompts.text({ message: message.trim() })),\n askSecret: (message) => answer(prompts.password({ message: message.trim() })),\n select: (message, labels) =>\n answer(\n prompts.select({\n message,\n options: labels.map((label, value) => ({ label, value })),\n }),\n ),\n confirm: (message) => answer(prompts.confirm({ message, initialValue: true })),\n print: (line) => prompts.log.info(line.trim()),\n close: () => {},\n };\n}\n\nasync function askRequired(io: OnboardIo, query: string, secret = false): Promise<string> {\n for (;;) {\n const value = (secret ? await io.askSecret(query) : await io.ask(query)).trim();\n if (value) return value;\n io.print(\" (required)\");\n }\n}\n\nasync function askChoice(io: OnboardIo, labels: string[], prompt: string): Promise<number> {\n return io.select(prompt, labels);\n}\n\nasync function askAdapter(\n io: OnboardIo,\n env: Record<string, string>,\n): Promise<{ githubRepos?: string[] }> {\n const platforms = ENV_MANIFEST.filter((group) => group.kind === \"platform\");\n io.print(\"\\nStep 1/3 — chat adapter\");\n const index = await askChoice(\n io,\n platforms.map((g) => g.title),\n \"Platform\",\n );\n const group = platforms[index]!;\n for (const spec of group.vars.filter((v) => v.required)) {\n env[spec.name] = await askRequired(io, ` ${spec.name} (${spec.doc}): `, spec.secret);\n }\n if (group.key !== \"github\") return {};\n const repos = await askRequired(io, \" Repositories (owner/repo or owner/*, comma-separated): \");\n return {\n githubRepos: repos\n .split(\",\")\n .map((repo) => repo.trim())\n .filter(Boolean),\n };\n}\n\nasync function askLlm(\n io: OnboardIo,\n env: Record<string, string>,\n): Promise<{ llm: OnboardLlmChoice; modelsJson?: string }> {\n io.print(\"\\nStep 2/3 — LLM provider\");\n const index = await askChoice(\n io,\n [\n \"Anthropic (ANTHROPIC_API_KEY)\",\n \"OpenAI (OPENAI_API_KEY)\",\n \"Custom OpenAI-compatible endpoint (vLLM, proxy, …) — writes models.json\",\n ],\n \"Provider\",\n );\n if (index === 0) {\n env.ANTHROPIC_API_KEY = await askRequired(io, \" ANTHROPIC_API_KEY: \", true);\n return { llm: { provider: \"anthropic\", model: \"claude-sonnet-4-6\" } };\n }\n if (index === 1) {\n env.OPENAI_API_KEY = await askRequired(io, \" OPENAI_API_KEY: \", true);\n return { llm: { provider: \"openai\", model: \"gpt-5.2\" } };\n }\n const provider = (await io.ask(\" Provider name [custom]: \")).trim() || \"custom\";\n const baseUrl = await askRequired(io, \" Base URL (e.g. http://host:8080/v1): \");\n const apiKey = (await io.askSecret(\" API key (empty if none): \")).trim();\n const ids = (await askRequired(io, \" Model ids (comma-separated): \"))\n .split(\",\")\n .map((id) => id.trim())\n .filter(Boolean);\n const modelsJson = JSON.stringify(\n {\n providers: {\n [provider]: {\n api: \"openai-completions\",\n baseUrl,\n apiKey: apiKey ? apiKey : undefined,\n models: ids.map((id) => ({ id })),\n },\n },\n },\n null,\n 2,\n );\n return { llm: { provider, model: ids[0]! }, modelsJson };\n}\n\nasync function askSandbox(io: OnboardIo): Promise<string | undefined> {\n io.print(\"\\nStep 3/3 — sandbox (where agent commands run)\");\n const index = await askChoice(\n io,\n [\n \"host — run directly on this machine\",\n \"image — one Docker container per conversation (needs Docker)\",\n ],\n \"Sandbox\",\n );\n return index === 1 ? \"image:ghcr.io/geminixiang/mikan-sandbox:latest\" : undefined;\n}\n\nexport function renderEnvFile(existing: string | undefined, vars: Record<string, string>): string {\n const pending = new Map(Object.entries(vars));\n const lines: string[] = [];\n for (const line of existing ? existing.split(\"\\n\") : []) {\n const rendered = rewriteEnvLine(line, pending);\n if (rendered.trim() || lines.length > 0) lines.push(rendered);\n }\n while (lines.at(-1) === \"\") lines.pop();\n for (const [key, value] of pending) lines.push(`${key}=${value}`);\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction rewriteEnvLine(line: string, pending: Map<string, string>): string {\n const key = /^([A-Za-z_][A-Za-z0-9_]*)=/.exec(line)?.[1];\n if (key === undefined || !pending.has(key)) return line;\n const value = pending.get(key);\n pending.delete(key);\n return `${key}=${value}`;\n}\n\nexport async function runOnboardWizard(\n stateDir: string,\n io: OnboardIo,\n paths?: { envFilePath?: string },\n): Promise<number> {\n const settingsPath = join(stateDir, \"settings.json\");\n if (existsSync(settingsPath)) {\n io.print(`Global settings already exists at ${settingsPath}.`);\n io.print(\"Remove it first to re-run onboarding, or edit it directly.\");\n return 1;\n }\n\n const env: Record<string, string> = {};\n const { githubRepos } = await askAdapter(io, env);\n const { llm, modelsJson } = await askLlm(io, env);\n const sandboxArg = await askSandbox(io);\n\n const modelsPath = join(stateDir, \"models.json\");\n if (modelsJson && existsSync(modelsPath)) {\n io.print(\n `models.json already exists at ${modelsPath}. Add the provider manually before retrying; nothing was written.`,\n );\n return 1;\n }\n io.print(\n `Review settings\\nProvider: ${llm.provider}\\nModel: ${llm.model}\\nSandbox: ${sandboxArg ?? \"host\"}\\nSettings: ${settingsPath}\\nCredentials: ${paths?.envFilePath ?? join(stateDir, \"mikan.env\")}${modelsJson ? `\\nModels: ${modelsPath}` : \"\"}\\nSecrets are hidden.`,\n );\n if (!(await io.confirm(\"Save these settings?\"))) {\n io.print(\"Onboarding cancelled; nothing was written.\");\n return 1;\n }\n\n createGlobalSettingsFile(stateDir, { llm, githubRepos });\n recordAllMigrations(stateDir);\n io.print(`\\nWrote ${settingsPath}`);\n\n if (modelsJson) {\n atomicWritePrivateFile(modelsPath, `${modelsJson}\\n`);\n io.print(`Wrote ${modelsPath}`);\n }\n\n const envFilePath = paths?.envFilePath ?? join(stateDir, \"mikan.env\");\n const existing = existsSync(envFilePath) ? readFileSync(envFilePath, \"utf-8\") : undefined;\n atomicWritePrivateFile(envFilePath, renderEnvFile(existing, env));\n io.print(`Wrote ${envFilePath} (0600)`);\n\n io.print(`\\n${envSetReport((name) => env[name] ?? readEnv(name))}`);\n\n const sandboxFlag = sandboxArg ? ` --sandbox ${sandboxArg}` : \"\";\n io.print(\"Next steps:\");\n io.print(` pm2: pm2 start ecosystem.config.cjs (loads ${envFilePath}; see deploy/pm2/)`);\n io.print(` direct: set -a; source ${envFilePath}; set +a; mikan${sandboxFlag}`);\n return 0;\n}\n\nexport async function runOnboardCommand(stateDir: string): Promise<number> {\n if (!process.stdin.isTTY) {\n const settingsPath = createGlobalSettingsFile(stateDir);\n recordAllMigrations(stateDir);\n console.log(`Created global settings at ${settingsPath}`);\n console.log(\n `Review the file, then start mikan (workspace defaults to ${join(stateDir, \"workspace\")}).`,\n );\n return 0;\n }\n prompts.intro(\"Mikan setup · arrows to choose · Esc / Ctrl+C to cancel\");\n const io = terminalIo();\n try {\n const code = await runOnboardWizard(stateDir, io);\n prompts.outro(code === 0 ? \"Setup complete\" : \"Setup not saved\");\n return code;\n } catch (err) {\n if (err instanceof OnboardAborted) {\n prompts.cancel(\"Onboarding aborted; nothing was written.\");\n return 1;\n }\n throw err;\n } finally {\n io.close();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"onboard.js","sourceRoot":"","sources":["../../src/cli/onboard.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport * as prompts from \"@clack/prompts\";\nimport { ENV_MANIFEST, envSetReport, readEnv } from \"../env-manifest.js\";\nimport { createGlobalSettingsFile } from \"../settings/index.js\";\nimport { recordAllMigrations } from \"../migrations/index.js\";\nimport type { OnboardLlmChoice } from \"../types.js\";\nimport { atomicWritePrivateFile } from \"../file-guards.js\";\nimport {\n MINIMAL_SANDBOX_IMAGE,\n PM2_ECOSYSTEM_FILENAME,\n PUBLISHED_SANDBOX_IMAGE,\n renderPm2Ecosystem,\n} from \"./pm2-ecosystem.js\";\nimport type { OnboardIo } from \"./types.js\";\n\nclass OnboardAborted extends Error {\n constructor() {\n super(\"Onboarding aborted.\");\n }\n}\n\nasync function answer<T>(result: Promise<T>): Promise<Exclude<T, symbol>> {\n const value = await result;\n if (prompts.isCancel(value)) throw new OnboardAborted();\n return value as Exclude<T, symbol>;\n}\n\nfunction terminalIo(): OnboardIo {\n return {\n ask: (message) => answer(prompts.text({ message: message.trim() })),\n askSecret: (message) => answer(prompts.password({ message: message.trim() })),\n select: (message, labels) =>\n answer(\n prompts.select({\n message,\n options: labels.map((label, value) => ({ label, value })),\n }),\n ),\n confirm: (message) => answer(prompts.confirm({ message, initialValue: true })),\n print: (line) => prompts.log.info(line.trim()),\n close: () => {},\n };\n}\n\nasync function askRequired(io: OnboardIo, query: string, secret = false): Promise<string> {\n for (;;) {\n const value = (secret ? await io.askSecret(query) : await io.ask(query)).trim();\n if (value) return value;\n io.print(\" (required)\");\n }\n}\n\nasync function askChoice(io: OnboardIo, labels: string[], prompt: string): Promise<number> {\n return io.select(prompt, labels);\n}\n\nasync function askAdapter(\n io: OnboardIo,\n env: Record<string, string>,\n): Promise<{ githubRepos?: string[] }> {\n const platforms = ENV_MANIFEST.filter((group) => group.kind === \"platform\");\n io.print(\"\\nStep 1/3 — chat adapter\");\n const index = await askChoice(\n io,\n platforms.map((g) => g.title),\n \"Platform\",\n );\n const group = platforms[index]!;\n for (const spec of group.vars.filter((v) => v.required)) {\n env[spec.name] = await askRequired(io, ` ${spec.name} (${spec.doc}): `, spec.secret);\n }\n if (group.key !== \"github\") return {};\n const repos = await askRequired(io, \" Repositories (owner/repo or owner/*, comma-separated): \");\n return {\n githubRepos: repos\n .split(\",\")\n .map((repo) => repo.trim())\n .filter(Boolean),\n };\n}\n\nasync function askLlm(\n io: OnboardIo,\n env: Record<string, string>,\n): Promise<{ llm: OnboardLlmChoice; modelsJson?: string }> {\n io.print(\"\\nStep 2/3 — LLM provider\");\n const index = await askChoice(\n io,\n [\n \"Anthropic (ANTHROPIC_API_KEY)\",\n \"OpenAI (OPENAI_API_KEY)\",\n \"Custom OpenAI-compatible endpoint (vLLM, proxy, …) — writes models.json\",\n ],\n \"Provider\",\n );\n if (index === 0) {\n env.ANTHROPIC_API_KEY = await askRequired(io, \" ANTHROPIC_API_KEY: \", true);\n return { llm: { provider: \"anthropic\", model: \"claude-sonnet-4-6\" } };\n }\n if (index === 1) {\n env.OPENAI_API_KEY = await askRequired(io, \" OPENAI_API_KEY: \", true);\n return { llm: { provider: \"openai\", model: \"gpt-5.2\" } };\n }\n const provider = (await io.ask(\" Provider name [custom]: \")).trim() || \"custom\";\n const baseUrl = await askRequired(io, \" Base URL (e.g. http://host:8080/v1): \");\n const apiKey = (await io.askSecret(\" API key (empty if none): \")).trim();\n const ids = (await askRequired(io, \" Model ids (comma-separated): \"))\n .split(\",\")\n .map((id) => id.trim())\n .filter(Boolean);\n const modelsJson = JSON.stringify(\n {\n providers: {\n [provider]: {\n api: \"openai-completions\",\n baseUrl,\n apiKey: apiKey ? apiKey : undefined,\n models: ids.map((id) => ({ id })),\n },\n },\n },\n null,\n 2,\n );\n return { llm: { provider, model: ids[0]! }, modelsJson };\n}\n\nasync function askSandboxImage(io: OnboardIo): Promise<string> {\n io.print(\"\\nStep 3/3 — sandbox image (one Docker container per conversation; needs Docker)\");\n const index = await askChoice(\n io,\n [\n `${PUBLISHED_SANDBOX_IMAGE} (recommended: git, gh, node, python, curl)`,\n `${MINIMAL_SANDBOX_IMAGE} (minimal: shell only, no git or package installs)`,\n \"Another image from Docker Hub or a registry…\",\n ],\n \"Sandbox image\",\n );\n if (index === 0) return PUBLISHED_SANDBOX_IMAGE;\n if (index === 1) return MINIMAL_SANDBOX_IMAGE;\n return askRequired(io, \" Image (e.g. registry/name:tag): \");\n}\n\nexport function renderEnvFile(existing: string | undefined, vars: Record<string, string>): string {\n const pending = new Map(Object.entries(vars));\n const lines: string[] = [];\n for (const line of existing ? existing.split(\"\\n\") : []) {\n const rendered = rewriteEnvLine(line, pending);\n if (rendered.trim() || lines.length > 0) lines.push(rendered);\n }\n while (lines.at(-1) === \"\") lines.pop();\n for (const [key, value] of pending) lines.push(`${key}=${value}`);\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction rewriteEnvLine(line: string, pending: Map<string, string>): string {\n const key = /^([A-Za-z_][A-Za-z0-9_]*)=/.exec(line)?.[1];\n if (key === undefined || !pending.has(key)) return line;\n const value = pending.get(key);\n pending.delete(key);\n return `${key}=${value}`;\n}\n\nexport async function runOnboardWizard(\n stateDir: string,\n io: OnboardIo,\n paths?: { envFilePath?: string },\n): Promise<number> {\n const settingsPath = join(stateDir, \"settings.json\");\n if (existsSync(settingsPath)) {\n io.print(`Global settings already exists at ${settingsPath}.`);\n io.print(\"Remove it first to re-run onboarding, or edit it directly.\");\n return 1;\n }\n\n const env: Record<string, string> = {};\n const { githubRepos } = await askAdapter(io, env);\n const { llm, modelsJson } = await askLlm(io, env);\n const image = await askSandboxImage(io);\n\n const modelsPath = join(stateDir, \"models.json\");\n if (modelsJson && existsSync(modelsPath)) {\n io.print(\n `models.json already exists at ${modelsPath}. Add the provider manually before retrying; nothing was written.`,\n );\n return 1;\n }\n io.print(\n `Review settings\\nProvider: ${llm.provider}\\nModel: ${llm.model}\\nSandbox: image:${image}\\nSettings: ${settingsPath}\\nCredentials: ${paths?.envFilePath ?? join(stateDir, \"mikan.env\")}${modelsJson ? `\\nModels: ${modelsPath}` : \"\"}\\nSecrets are hidden.`,\n );\n if (!(await io.confirm(\"Save these settings?\"))) {\n io.print(\"Onboarding cancelled; nothing was written.\");\n return 1;\n }\n\n createGlobalSettingsFile(stateDir, { llm, githubRepos });\n recordAllMigrations(stateDir);\n io.print(`\\nWrote ${settingsPath}`);\n\n if (modelsJson) {\n atomicWritePrivateFile(modelsPath, `${modelsJson}\\n`);\n io.print(`Wrote ${modelsPath}`);\n }\n\n const envFilePath = paths?.envFilePath ?? join(stateDir, \"mikan.env\");\n const existing = existsSync(envFilePath) ? readFileSync(envFilePath, \"utf-8\") : undefined;\n atomicWritePrivateFile(envFilePath, renderEnvFile(existing, env));\n io.print(`Wrote ${envFilePath} (0600)`);\n\n io.print(`\\n${envSetReport((name) => env[name] ?? readEnv(name))}`);\n\n const ecosystemPath = join(stateDir, PM2_ECOSYSTEM_FILENAME);\n if (existsSync(ecosystemPath)) {\n io.print(`Kept existing ${ecosystemPath}`);\n } else {\n atomicWritePrivateFile(ecosystemPath, renderPm2Ecosystem(image));\n io.print(`Wrote ${ecosystemPath}`);\n }\n\n io.print(\"Next steps:\");\n io.print(` 1. docker pull ${image}`);\n io.print(` 2. pm2 start ${ecosystemPath} && pm2 save`);\n io.print(\n ` Foreground instead: set -a; source ${envFilePath}; set +a; mikan --sandbox=image:${image}`,\n );\n return 0;\n}\n\nexport async function runOnboardCommand(stateDir: string): Promise<number> {\n if (!process.stdin.isTTY) {\n const settingsPath = createGlobalSettingsFile(stateDir);\n recordAllMigrations(stateDir);\n console.log(`Created global settings at ${settingsPath}`);\n console.log(\n `Review the file, then start mikan (workspace defaults to ${join(stateDir, \"workspace\")}).`,\n );\n return 0;\n }\n prompts.intro(\"Mikan setup · arrows to choose · Esc / Ctrl+C to cancel\");\n const io = terminalIo();\n try {\n const code = await runOnboardWizard(stateDir, io);\n prompts.outro(code === 0 ? \"Setup complete\" : \"Setup not saved\");\n return code;\n } catch (err) {\n if (err instanceof OnboardAborted) {\n prompts.cancel(\"Onboarding aborted; nothing was written.\");\n return 1;\n }\n throw err;\n } finally {\n io.close();\n }\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const PUBLISHED_SANDBOX_IMAGE = "ghcr.io/geminixiang/mikan-sandbox:latest";
|
|
2
|
+
export declare const MINIMAL_SANDBOX_IMAGE = "debian:trixie-slim";
|
|
3
|
+
export declare const PM2_ECOSYSTEM_FILENAME = "ecosystem.config.cjs";
|
|
4
|
+
export declare function renderPm2Ecosystem(image: string): string;
|
|
5
|
+
//# sourceMappingURL=pm2-ecosystem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pm2-ecosystem.d.ts","sourceRoot":"","sources":["../../src/cli/pm2-ecosystem.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,6CAA6C,CAAC;AAElF,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAE1D,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAE7D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAqDxD"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export const PUBLISHED_SANDBOX_IMAGE = "ghcr.io/geminixiang/mikan-sandbox:latest";
|
|
2
|
+
export const MINIMAL_SANDBOX_IMAGE = "debian:trixie-slim";
|
|
3
|
+
export const PM2_ECOSYSTEM_FILENAME = "ecosystem.config.cjs";
|
|
4
|
+
export function renderPm2Ecosystem(image) {
|
|
5
|
+
return `const fs = require("node:fs");
|
|
6
|
+
const os = require("node:os");
|
|
7
|
+
const path = require("node:path");
|
|
8
|
+
|
|
9
|
+
function loadEnvFile(file) {
|
|
10
|
+
let text;
|
|
11
|
+
try {
|
|
12
|
+
text = fs.readFileSync(file, "utf-8");
|
|
13
|
+
} catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
const env = {};
|
|
17
|
+
for (const line of text.split("\\n")) {
|
|
18
|
+
const trimmed = line.trim();
|
|
19
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
20
|
+
const eq = trimmed.indexOf("=");
|
|
21
|
+
if (eq <= 0) continue;
|
|
22
|
+
const key = trimmed.slice(0, eq).trim();
|
|
23
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
24
|
+
if (
|
|
25
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
26
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
27
|
+
) {
|
|
28
|
+
value = value.slice(1, -1);
|
|
29
|
+
}
|
|
30
|
+
env[key] = value;
|
|
31
|
+
}
|
|
32
|
+
return env;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
apps: [
|
|
37
|
+
{
|
|
38
|
+
name: "mikan",
|
|
39
|
+
script: "mikan",
|
|
40
|
+
|
|
41
|
+
args: ${JSON.stringify(`--sandbox=image:${image}`)},
|
|
42
|
+
|
|
43
|
+
env: loadEnvFile(path.join(os.homedir(), ".mikan", "mikan.env")),
|
|
44
|
+
|
|
45
|
+
kill_timeout: 360000,
|
|
46
|
+
|
|
47
|
+
autorestart: true,
|
|
48
|
+
max_restarts: 10,
|
|
49
|
+
restart_delay: 2000,
|
|
50
|
+
|
|
51
|
+
time: true,
|
|
52
|
+
merge_logs: true,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=pm2-ecosystem.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pm2-ecosystem.js","sourceRoot":"","sources":["../../src/cli/pm2-ecosystem.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAAG,0CAA0C,CAAC;AAElF,MAAM,CAAC,MAAM,qBAAqB,GAAG,oBAAoB,CAAC;AAE1D,MAAM,CAAC,MAAM,sBAAsB,GAAG,sBAAsB,CAAC;AAE7D,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAoCK,IAAI,CAAC,SAAS,CAAC,mBAAmB,KAAK,EAAE,CAAC;;;;;;;;;;;;;;;CAevD,CAAC;AACF,CAAC","sourcesContent":["export const PUBLISHED_SANDBOX_IMAGE = \"ghcr.io/geminixiang/mikan-sandbox:latest\";\n\nexport const MINIMAL_SANDBOX_IMAGE = \"debian:trixie-slim\";\n\nexport const PM2_ECOSYSTEM_FILENAME = \"ecosystem.config.cjs\";\n\nexport function renderPm2Ecosystem(image: string): string {\n return `const fs = require(\"node:fs\");\nconst os = require(\"node:os\");\nconst path = require(\"node:path\");\n\nfunction loadEnvFile(file) {\n let text;\n try {\n text = fs.readFileSync(file, \"utf-8\");\n } catch {\n return {};\n }\n const env = {};\n for (const line of text.split(\"\\\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eq = trimmed.indexOf(\"=\");\n if (eq <= 0) continue;\n const key = trimmed.slice(0, eq).trim();\n let value = trimmed.slice(eq + 1).trim();\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n env[key] = value;\n }\n return env;\n}\n\nmodule.exports = {\n apps: [\n {\n name: \"mikan\",\n script: \"mikan\",\n\n args: ${JSON.stringify(`--sandbox=image:${image}`)},\n\n env: loadEnvFile(path.join(os.homedir(), \".mikan\", \"mikan.env\")),\n\n kill_timeout: 360000,\n\n autorestart: true,\n max_restarts: 10,\n restart_delay: 2000,\n\n time: true,\n merge_logs: true,\n },\n ],\n};\n`;\n}\n"]}
|
|
@@ -6,6 +6,7 @@ export declare class ContainerExecutor implements Executor {
|
|
|
6
6
|
private readonly hostExecutor;
|
|
7
7
|
constructor(container: string, env?: Record<string, string> | undefined, ensureReady?: (() => Promise<void>) | undefined);
|
|
8
8
|
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
9
|
+
private killProcessGroup;
|
|
9
10
|
readFile(path: string, options?: ExecOptions): Promise<string>;
|
|
10
11
|
readFileBase64(path: string, options?: ExecOptions): Promise<string>;
|
|
11
12
|
writeFile(path: string, content: string, options?: ExecOptions): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../src/sandbox/container.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../src/sandbox/container.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,sBAAsB,EACtB,WAAW,EACX,UAAU,EACV,QAAQ,EACR,kBAAkB,EAClB,cAAc,EACf,MAAM,YAAY,CAAC;AAmHpB,qBAAa,iBAAkB,YAAW,QAAQ;IAI9C,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,GAAG,CAAC;IACZ,OAAO,CAAC,WAAW,CAAC;IALtB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IAEnD,YACU,SAAS,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAA,EAC5B,WAAW,CAAC,GAAE,MAAM,OAAO,CAAC,IAAI,CAAC,aAAA,EACvC;IAEE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CA0BtE;YAEa,gBAAgB;IAW9B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAE7D;IAED,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAEnE;IAED,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7E;IAED,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1C;IAED,cAAc,CAAC,iBAAiB,EAAE,MAAM,GAAG,kBAAkB,CAE5D;IAED,gBAAgB,IAAI,sBAAsB,CAEzC;CACF;AAED,eAAO,MAAM,uBAAuB,EAAE,cAAc,CAAC,sBAAsB,CAQ1E,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { tmpdir } from "node:os";
|
|
3
4
|
import { join } from "node:path";
|
|
@@ -5,6 +6,7 @@ import { SandboxError, createMountedRuntimePathContext, execReadFile, execReadFi
|
|
|
5
6
|
import { HostExecutor } from "./host.js";
|
|
6
7
|
import { GUEST_WORKSPACE_ROOT } from "./layout.js";
|
|
7
8
|
import { errorMessage } from "../unknown-values.js";
|
|
9
|
+
import * as log from "../log.js";
|
|
8
10
|
const PRIVATE_DIR_MODE = 0o700;
|
|
9
11
|
const PRIVATE_FILE_MODE = 0o600;
|
|
10
12
|
function parseContainerSandboxArg(value) {
|
|
@@ -47,10 +49,25 @@ async function validateContainerSandbox(config) {
|
|
|
47
49
|
}
|
|
48
50
|
console.log(` Container '${config.container}' is running.`);
|
|
49
51
|
}
|
|
50
|
-
|
|
52
|
+
const RUN_IN_PROCESS_GROUP = [
|
|
53
|
+
"if command -v setsid >/dev/null 2>&1; then",
|
|
54
|
+
`setsid sh -c 'echo $$ > "$1"; exec sh -c "$2"' _ "$1" "$2" & wait $!;`,
|
|
55
|
+
'status=$?; rm -f "$1"; exit $status;',
|
|
56
|
+
'fi; exec sh -c "$2"',
|
|
57
|
+
].join(" ");
|
|
58
|
+
const KILL_PROCESS_GROUP = [
|
|
59
|
+
'i=0; while [ ! -s "$1" ] && [ $i -lt 10 ]; do sleep 0.1; i=$((i+1)); done;',
|
|
60
|
+
'group=$(cat "$1" 2>/dev/null) || exit 0; rm -f "$1";',
|
|
61
|
+
'kill -s KILL -- -"$group" 2>/dev/null || kill -s KILL -"$group" 2>/dev/null; exit 0',
|
|
62
|
+
].join(" ");
|
|
63
|
+
function buildContainerExecCommand(container, command, groupFile, envFilePath, cwd) {
|
|
51
64
|
const envPart = envFilePath ? `--env-file ${shellEscape(envFilePath)} ` : "";
|
|
52
65
|
const workdir = cwd === undefined ? GUEST_WORKSPACE_ROOT : shellEscape(cwd);
|
|
53
|
-
|
|
66
|
+
const script = `${shellEscape(RUN_IN_PROCESS_GROUP)} _ ${shellEscape(groupFile)} ${shellEscape(command)}`;
|
|
67
|
+
return `docker exec ${envPart}-w ${workdir} ${container} sh -c ${script}`;
|
|
68
|
+
}
|
|
69
|
+
function buildKillProcessGroupCommand(container, groupFile) {
|
|
70
|
+
return `docker exec ${container} sh -c ${shellEscape(KILL_PROCESS_GROUP)} _ ${shellEscape(groupFile)}`;
|
|
54
71
|
}
|
|
55
72
|
const GITHUB_CREDENTIAL_KEY = "credential.https://github.com.helper";
|
|
56
73
|
function withGitHubCredentialHelper(env) {
|
|
@@ -85,15 +102,28 @@ export class ContainerExecutor {
|
|
|
85
102
|
}
|
|
86
103
|
const env = withGitHubCredentialHelper(this.env);
|
|
87
104
|
const temp = env ? createSecureEnvFile(env) : undefined;
|
|
105
|
+
const groupFile = `/tmp/mikan-exec-${randomUUID()}`;
|
|
88
106
|
try {
|
|
89
|
-
const dockerCmd = buildContainerExecCommand(this.container, command, temp?.envFilePath, options?.cwd);
|
|
107
|
+
const dockerCmd = buildContainerExecCommand(this.container, command, groupFile, temp?.envFilePath, options?.cwd);
|
|
90
108
|
const { cwd: _cwd, ...hostOptions } = options ?? {};
|
|
91
109
|
return await this.hostExecutor.exec(dockerCmd, options ? hostOptions : undefined);
|
|
92
110
|
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
await this.killProcessGroup(groupFile);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
93
115
|
finally {
|
|
94
116
|
temp?.cleanup();
|
|
95
117
|
}
|
|
96
118
|
}
|
|
119
|
+
async killProcessGroup(groupFile) {
|
|
120
|
+
try {
|
|
121
|
+
await this.hostExecutor.exec(buildKillProcessGroupCommand(this.container, groupFile));
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
log.logWarning(`Could not stop a command in container ${this.container}`, errorMessage(error));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
97
127
|
readFile(path, options) {
|
|
98
128
|
return execReadFile(this, path, options);
|
|
99
129
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/sandbox/container.ts"],"names":[],"mappings":"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","sourcesContent":["import { chmodSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type {\n ContainerSandboxConfig,\n ExecOptions,\n ExecResult,\n Executor,\n RuntimePathContext,\n SandboxAdapter,\n} from \"./types.js\";\nimport {\n SandboxError,\n createMountedRuntimePathContext,\n execReadFile,\n execReadFileBase64,\n execSimple,\n execWriteFile,\n shellEscape,\n} from \"./utils.js\";\nimport { HostExecutor } from \"./host.js\";\nimport { GUEST_WORKSPACE_ROOT } from \"./layout.js\";\nimport { errorMessage } from \"../unknown-values.js\";\n\nconst PRIVATE_DIR_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\nfunction parseContainerSandboxArg(value: string): ContainerSandboxConfig | undefined {\n if (!value.startsWith(\"container:\")) {\n return undefined;\n }\n\n const container = value.slice(\"container:\".length);\n if (!container) {\n throw new SandboxError(\n \"Error: container sandbox requires container name (e.g., container:mikan-sandbox)\",\n );\n }\n return { type: \"container\", container };\n}\n\nasync function validateContainerSandbox(config: ContainerSandboxConfig): Promise<void> {\n try {\n await execSimple(\"docker\", [\"--version\"]);\n } catch {\n throw new SandboxError(\"Error: Docker is not installed or not in PATH\");\n }\n\n try {\n const result = await execSimple(\"docker\", [\n \"inspect\",\n \"-f\",\n \"{{.State.Running}}\",\n config.container,\n ]);\n if (result.trim() !== \"true\") {\n throw new SandboxError(`Error: Container '${config.container}' is not running.`, [\n `Start it with: docker start ${config.container}`,\n ]);\n }\n } catch (error) {\n if (error instanceof SandboxError) {\n throw error;\n }\n throw new SandboxError(`Error: Container '${config.container}' does not exist.`, [\n `Create it with: docker run -d --name ${config.container} -v <workspace>:/workspace alpine:latest sleep infinity`,\n ]);\n }\n\n console.log(` Container '${config.container}' is running.`);\n}\n\nfunction buildContainerExecCommand(\n container: string,\n command: string,\n envFilePath?: string,\n cwd?: string,\n): string {\n const envPart = envFilePath ? `--env-file ${shellEscape(envFilePath)} ` : \"\";\n const workdir = cwd === undefined ? GUEST_WORKSPACE_ROOT : shellEscape(cwd);\n return `docker exec ${envPart}-w ${workdir} ${container} sh -c ${shellEscape(command)}`;\n}\n\nconst GITHUB_CREDENTIAL_KEY = \"credential.https://github.com.helper\";\n\nfunction withGitHubCredentialHelper(\n env?: Record<string, string>,\n): Record<string, string> | undefined {\n if (!env || !hasGitHubToken(env) || env.GIT_CONFIG_COUNT !== undefined) {\n return env;\n }\n return {\n ...env,\n GIT_CONFIG_COUNT: \"2\",\n GIT_CONFIG_KEY_0: GITHUB_CREDENTIAL_KEY,\n GIT_CONFIG_VALUE_0: \"\",\n GIT_CONFIG_KEY_1: GITHUB_CREDENTIAL_KEY,\n GIT_CONFIG_VALUE_1: \"!gh auth git-credential\",\n };\n}\n\nfunction hasGitHubToken(env: Record<string, string>): boolean {\n return Boolean(env.GH_TOKEN || env.GITHUB_TOKEN);\n}\n\nexport class ContainerExecutor implements Executor {\n private readonly hostExecutor = new HostExecutor();\n\n constructor(\n private container: string,\n private env?: Record<string, string>,\n private ensureReady?: () => Promise<void>,\n ) {}\n\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n if (this.ensureReady) {\n await this.ensureReady();\n } else {\n await ensureContainerRunning(this.container);\n }\n\n const env = withGitHubCredentialHelper(this.env);\n const temp = env ? createSecureEnvFile(env) : undefined;\n try {\n const dockerCmd = buildContainerExecCommand(\n this.container,\n command,\n temp?.envFilePath,\n options?.cwd,\n );\n const { cwd: _cwd, ...hostOptions } = options ?? {};\n return await this.hostExecutor.exec(dockerCmd, options ? hostOptions : undefined);\n } finally {\n temp?.cleanup();\n }\n }\n\n readFile(path: string, options?: ExecOptions): Promise<string> {\n return execReadFile(this, path, options);\n }\n\n readFileBase64(path: string, options?: ExecOptions): Promise<string> {\n return execReadFileBase64(this, path, options);\n }\n\n writeFile(path: string, content: string, options?: ExecOptions): Promise<void> {\n return execWriteFile(this, path, content, options);\n }\n\n getWorkspacePath(_hostPath: string): string {\n return GUEST_WORKSPACE_ROOT;\n }\n\n getPathContext(hostWorkspaceRoot: string): RuntimePathContext {\n return createMountedRuntimePathContext(hostWorkspaceRoot, GUEST_WORKSPACE_ROOT);\n }\n\n getSandboxConfig(): ContainerSandboxConfig {\n return { type: \"container\", container: this.container };\n }\n}\n\nexport const containerSandboxAdapter: SandboxAdapter<ContainerSandboxConfig> = {\n type: \"container\",\n credentials: { env: true, fileMounts: false },\n workspace: { managedProjection: false },\n parse: parseContainerSandboxArg,\n validate: validateContainerSandbox,\n createExecutor: (config, env, ensureReady) =>\n new ContainerExecutor(config.container, env, ensureReady),\n};\n\nasync function ensureContainerRunning(container: string): Promise<void> {\n try {\n const running = await execSimple(\"docker\", [\"inspect\", \"-f\", \"{{.State.Running}}\", container]);\n if (running.trim() === \"true\") {\n return;\n }\n await execSimple(\"docker\", [\"start\", container]);\n } catch (error) {\n const details = errorMessage(error);\n throw new Error(\n `Container \"${container}\" is not available. ` +\n `Expected a pre-existing container or image provisioning to keep it running.\\n${details}`.trim(),\n { cause: error },\n );\n }\n}\n\nfunction createSecureEnvFile(env: Record<string, string>): {\n envFilePath: string;\n cleanup: () => void;\n} {\n const tempDir = mkdtempSync(join(tmpdir(), \"mikan-docker-env-\"));\n chmodSync(tempDir, PRIVATE_DIR_MODE);\n const envFilePath = join(tempDir, \"env.list\");\n const content =\n Object.entries(env)\n .map(([key, value]) => `${key}=${value.replace(/\\r?\\n/g, \"\")}`)\n .join(\"\\n\") + \"\\n\";\n writeFileSync(envFilePath, content, { encoding: \"utf-8\", mode: PRIVATE_FILE_MODE });\n chmodSync(envFilePath, PRIVATE_FILE_MODE);\n\n return {\n envFilePath,\n cleanup: () => {\n rmSync(tempDir, { recursive: true, force: true });\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/sandbox/container.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { chmodSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type {\n ContainerSandboxConfig,\n ExecOptions,\n ExecResult,\n Executor,\n RuntimePathContext,\n SandboxAdapter,\n} from \"./types.js\";\nimport {\n SandboxError,\n createMountedRuntimePathContext,\n execReadFile,\n execReadFileBase64,\n execSimple,\n execWriteFile,\n shellEscape,\n} from \"./utils.js\";\nimport { HostExecutor } from \"./host.js\";\nimport { GUEST_WORKSPACE_ROOT } from \"./layout.js\";\nimport { errorMessage } from \"../unknown-values.js\";\nimport * as log from \"../log.js\";\n\nconst PRIVATE_DIR_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\nfunction parseContainerSandboxArg(value: string): ContainerSandboxConfig | undefined {\n if (!value.startsWith(\"container:\")) {\n return undefined;\n }\n\n const container = value.slice(\"container:\".length);\n if (!container) {\n throw new SandboxError(\n \"Error: container sandbox requires container name (e.g., container:mikan-sandbox)\",\n );\n }\n return { type: \"container\", container };\n}\n\nasync function validateContainerSandbox(config: ContainerSandboxConfig): Promise<void> {\n try {\n await execSimple(\"docker\", [\"--version\"]);\n } catch {\n throw new SandboxError(\"Error: Docker is not installed or not in PATH\");\n }\n\n try {\n const result = await execSimple(\"docker\", [\n \"inspect\",\n \"-f\",\n \"{{.State.Running}}\",\n config.container,\n ]);\n if (result.trim() !== \"true\") {\n throw new SandboxError(`Error: Container '${config.container}' is not running.`, [\n `Start it with: docker start ${config.container}`,\n ]);\n }\n } catch (error) {\n if (error instanceof SandboxError) {\n throw error;\n }\n throw new SandboxError(`Error: Container '${config.container}' does not exist.`, [\n `Create it with: docker run -d --name ${config.container} -v <workspace>:/workspace alpine:latest sleep infinity`,\n ]);\n }\n\n console.log(` Container '${config.container}' is running.`);\n}\n\nconst RUN_IN_PROCESS_GROUP = [\n \"if command -v setsid >/dev/null 2>&1; then\",\n `setsid sh -c 'echo $$ > \"$1\"; exec sh -c \"$2\"' _ \"$1\" \"$2\" & wait $!;`,\n 'status=$?; rm -f \"$1\"; exit $status;',\n 'fi; exec sh -c \"$2\"',\n].join(\" \");\n\nconst KILL_PROCESS_GROUP = [\n 'i=0; while [ ! -s \"$1\" ] && [ $i -lt 10 ]; do sleep 0.1; i=$((i+1)); done;',\n 'group=$(cat \"$1\" 2>/dev/null) || exit 0; rm -f \"$1\";',\n 'kill -s KILL -- -\"$group\" 2>/dev/null || kill -s KILL -\"$group\" 2>/dev/null; exit 0',\n].join(\" \");\n\nfunction buildContainerExecCommand(\n container: string,\n command: string,\n groupFile: string,\n envFilePath?: string,\n cwd?: string,\n): string {\n const envPart = envFilePath ? `--env-file ${shellEscape(envFilePath)} ` : \"\";\n const workdir = cwd === undefined ? GUEST_WORKSPACE_ROOT : shellEscape(cwd);\n const script = `${shellEscape(RUN_IN_PROCESS_GROUP)} _ ${shellEscape(groupFile)} ${shellEscape(command)}`;\n return `docker exec ${envPart}-w ${workdir} ${container} sh -c ${script}`;\n}\n\nfunction buildKillProcessGroupCommand(container: string, groupFile: string): string {\n return `docker exec ${container} sh -c ${shellEscape(KILL_PROCESS_GROUP)} _ ${shellEscape(groupFile)}`;\n}\n\nconst GITHUB_CREDENTIAL_KEY = \"credential.https://github.com.helper\";\n\nfunction withGitHubCredentialHelper(\n env?: Record<string, string>,\n): Record<string, string> | undefined {\n if (!env || !hasGitHubToken(env) || env.GIT_CONFIG_COUNT !== undefined) {\n return env;\n }\n return {\n ...env,\n GIT_CONFIG_COUNT: \"2\",\n GIT_CONFIG_KEY_0: GITHUB_CREDENTIAL_KEY,\n GIT_CONFIG_VALUE_0: \"\",\n GIT_CONFIG_KEY_1: GITHUB_CREDENTIAL_KEY,\n GIT_CONFIG_VALUE_1: \"!gh auth git-credential\",\n };\n}\n\nfunction hasGitHubToken(env: Record<string, string>): boolean {\n return Boolean(env.GH_TOKEN || env.GITHUB_TOKEN);\n}\n\nexport class ContainerExecutor implements Executor {\n private readonly hostExecutor = new HostExecutor();\n\n constructor(\n private container: string,\n private env?: Record<string, string>,\n private ensureReady?: () => Promise<void>,\n ) {}\n\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n if (this.ensureReady) {\n await this.ensureReady();\n } else {\n await ensureContainerRunning(this.container);\n }\n\n const env = withGitHubCredentialHelper(this.env);\n const temp = env ? createSecureEnvFile(env) : undefined;\n const groupFile = `/tmp/mikan-exec-${randomUUID()}`;\n try {\n const dockerCmd = buildContainerExecCommand(\n this.container,\n command,\n groupFile,\n temp?.envFilePath,\n options?.cwd,\n );\n const { cwd: _cwd, ...hostOptions } = options ?? {};\n return await this.hostExecutor.exec(dockerCmd, options ? hostOptions : undefined);\n } catch (error) {\n await this.killProcessGroup(groupFile);\n throw error;\n } finally {\n temp?.cleanup();\n }\n }\n\n private async killProcessGroup(groupFile: string): Promise<void> {\n try {\n await this.hostExecutor.exec(buildKillProcessGroupCommand(this.container, groupFile));\n } catch (error) {\n log.logWarning(\n `Could not stop a command in container ${this.container}`,\n errorMessage(error),\n );\n }\n }\n\n readFile(path: string, options?: ExecOptions): Promise<string> {\n return execReadFile(this, path, options);\n }\n\n readFileBase64(path: string, options?: ExecOptions): Promise<string> {\n return execReadFileBase64(this, path, options);\n }\n\n writeFile(path: string, content: string, options?: ExecOptions): Promise<void> {\n return execWriteFile(this, path, content, options);\n }\n\n getWorkspacePath(_hostPath: string): string {\n return GUEST_WORKSPACE_ROOT;\n }\n\n getPathContext(hostWorkspaceRoot: string): RuntimePathContext {\n return createMountedRuntimePathContext(hostWorkspaceRoot, GUEST_WORKSPACE_ROOT);\n }\n\n getSandboxConfig(): ContainerSandboxConfig {\n return { type: \"container\", container: this.container };\n }\n}\n\nexport const containerSandboxAdapter: SandboxAdapter<ContainerSandboxConfig> = {\n type: \"container\",\n credentials: { env: true, fileMounts: false },\n workspace: { managedProjection: false },\n parse: parseContainerSandboxArg,\n validate: validateContainerSandbox,\n createExecutor: (config, env, ensureReady) =>\n new ContainerExecutor(config.container, env, ensureReady),\n};\n\nasync function ensureContainerRunning(container: string): Promise<void> {\n try {\n const running = await execSimple(\"docker\", [\"inspect\", \"-f\", \"{{.State.Running}}\", container]);\n if (running.trim() === \"true\") {\n return;\n }\n await execSimple(\"docker\", [\"start\", container]);\n } catch (error) {\n const details = errorMessage(error);\n throw new Error(\n `Container \"${container}\" is not available. ` +\n `Expected a pre-existing container or image provisioning to keep it running.\\n${details}`.trim(),\n { cause: error },\n );\n }\n}\n\nfunction createSecureEnvFile(env: Record<string, string>): {\n envFilePath: string;\n cleanup: () => void;\n} {\n const tempDir = mkdtempSync(join(tmpdir(), \"mikan-docker-env-\"));\n chmodSync(tempDir, PRIVATE_DIR_MODE);\n const envFilePath = join(tempDir, \"env.list\");\n const content =\n Object.entries(env)\n .map(([key, value]) => `${key}=${value.replace(/\\r?\\n/g, \"\")}`)\n .join(\"\\n\") + \"\\n\";\n writeFileSync(envFilePath, content, { encoding: \"utf-8\", mode: PRIVATE_FILE_MODE });\n chmodSync(envFilePath, PRIVATE_FILE_MODE);\n\n return {\n envFilePath,\n cleanup: () => {\n rmSync(tempDir, { recursive: true, force: true });\n },\n };\n}\n"]}
|
package/package.json
CHANGED