dockai 0.2.0
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/LICENSE +21 -0
- package/README.md +61 -0
- package/dist/api.js +17 -0
- package/dist/api.js.map +1 -0
- package/dist/commands/claude.js +292 -0
- package/dist/commands/claude.js.map +1 -0
- package/dist/commands/connect.js +55 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/deploy.js +98 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/login.js +92 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.js +10 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/logs.js +26 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/open.js +26 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/paste.js +63 -0
- package/dist/commands/paste.js.map +1 -0
- package/dist/commands/projects.js +31 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/proxy.js +55 -0
- package/dist/commands/proxy.js.map +1 -0
- package/dist/commands/push.js +53 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/sessions.js +330 -0
- package/dist/commands/sessions.js.map +1 -0
- package/dist/commands/ssh-config.js +104 -0
- package/dist/commands/ssh-config.js.map +1 -0
- package/dist/commands/start-stop.js +37 -0
- package/dist/commands/start-stop.js.map +1 -0
- package/dist/commands/status.js +38 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/whoami.js +27 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/config.js +34 -0
- package/dist/config.js.map +1 -0
- package/dist/index.js +154 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/client-tools.js +175 -0
- package/dist/lib/client-tools.js.map +1 -0
- package/dist/lib/pick-project.js +176 -0
- package/dist/lib/pick-project.js.map +1 -0
- package/dist/lib/remote.js +161 -0
- package/dist/lib/remote.js.map +1 -0
- package/dist/terminal.js +80 -0
- package/dist/terminal.js.map +1 -0
- package/dist/version.js +17 -0
- package/dist/version.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { assertRunning, explainSshFailure, pushTree, workspaceDir } from "../lib/remote.js";
|
|
5
|
+
import { createApiClient } from "../api.js";
|
|
6
|
+
import { claudeCommand } from "./claude.js";
|
|
7
|
+
/** Files whose contents mention paths and can be rewritten as text. */
|
|
8
|
+
const TEXT_EXTENSIONS = new Set([".jsonl", ".json", ".md", ".txt"]);
|
|
9
|
+
/**
|
|
10
|
+
* Claude's session directory name for a working directory: every `/` becomes
|
|
11
|
+
* `-`, so /Users/you/Projects/foo is stored as -Users-you-Projects-foo.
|
|
12
|
+
*/
|
|
13
|
+
function sessionDirName(dir) {
|
|
14
|
+
return dir.replace(/\//g, "-");
|
|
15
|
+
}
|
|
16
|
+
function escapeRegExp(s) {
|
|
17
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18
|
+
}
|
|
19
|
+
async function exists(p) {
|
|
20
|
+
try {
|
|
21
|
+
await fs.access(p);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Copy a directory's Claude Code sessions into a project.
|
|
30
|
+
*
|
|
31
|
+
* Sessions are plain files under ~/.claude/projects/<encoded cwd>/ — one
|
|
32
|
+
* JSONL transcript per session, a folder of tool output per session, and the
|
|
33
|
+
* project's auto-memory — so moving them is a copy. What makes the copy
|
|
34
|
+
* *work* on the far end is the path rewrite: transcripts record the directory
|
|
35
|
+
* they ran in and every file they touched as absolute paths, and a resumed
|
|
36
|
+
* session that believes it lives in /Users/you/Projects/foo will read from
|
|
37
|
+
* there. Rewriting that to /workspace/<slug> (and the home directory to the
|
|
38
|
+
* worker's) makes the history describe the workspace it now sits in.
|
|
39
|
+
*
|
|
40
|
+
* The rewrite happens in a staging copy; the local sessions are never
|
|
41
|
+
* modified. Sessions already on the worker are left alone unless
|
|
42
|
+
* --overwrite is given, because the copy there may have been continued
|
|
43
|
+
* since and is then the newer one.
|
|
44
|
+
*/
|
|
45
|
+
export async function sessionsPushCommand(slug, options) {
|
|
46
|
+
const localProject = path.resolve(options.from ?? process.cwd());
|
|
47
|
+
const home = os.homedir();
|
|
48
|
+
const projectsRoot = path.join(home, ".claude", "projects");
|
|
49
|
+
// The directory as Claude names it. Fall back to the stricter encoding in
|
|
50
|
+
// case a future CLI version widens what it replaces.
|
|
51
|
+
const candidates = [
|
|
52
|
+
sessionDirName(localProject),
|
|
53
|
+
localProject.replace(/[^A-Za-z0-9-]/g, "-"),
|
|
54
|
+
];
|
|
55
|
+
let source = null;
|
|
56
|
+
for (const name of candidates) {
|
|
57
|
+
if (await exists(path.join(projectsRoot, name))) {
|
|
58
|
+
source = path.join(projectsRoot, name);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!source) {
|
|
63
|
+
console.error(`No Claude sessions found for ${localProject}`);
|
|
64
|
+
console.error(` (looked for ${path.join(projectsRoot, candidates[0])})`);
|
|
65
|
+
console.error("Run this from the project directory, or pass --from <dir>.");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
const remoteWorkspace = workspaceDir(slug);
|
|
69
|
+
const remoteSessionDir = `.claude/projects/${sessionDirName(remoteWorkspace)}`;
|
|
70
|
+
// Most specific first, so the project path is rewritten before the home
|
|
71
|
+
// directory it sits under.
|
|
72
|
+
//
|
|
73
|
+
// Case-insensitive on macOS, whose default filesystem is: a transcript
|
|
74
|
+
// records the directory as it was typed, so `cd NorwayTrip` into a folder
|
|
75
|
+
// named Norwaytrip leaves every path in the history spelled differently
|
|
76
|
+
// from the folder — and an exact match rewrites none of them.
|
|
77
|
+
//
|
|
78
|
+
// The project is also written as `~/<rel>` — in prose, and in the shell
|
|
79
|
+
// commands the history records — whenever it lives under the home
|
|
80
|
+
// directory, so that spelling is rewritten too.
|
|
81
|
+
const flags = process.platform === "darwin" ? "gi" : "g";
|
|
82
|
+
const pairs = [[localProject, remoteWorkspace]];
|
|
83
|
+
if (localProject.startsWith(home + path.sep)) {
|
|
84
|
+
pairs.push(["~" + localProject.slice(home.length), remoteWorkspace]);
|
|
85
|
+
}
|
|
86
|
+
pairs.push([sessionDirName(localProject), sessionDirName(remoteWorkspace)], [home, "/home/claude"]);
|
|
87
|
+
const rewrites = pairs.map(([from, to]) => [new RegExp(escapeRegExp(from), flags), to]);
|
|
88
|
+
// Which sessions to send. A session is its top-level transcript plus the
|
|
89
|
+
// folder of the same id holding its tool output; everything else in the
|
|
90
|
+
// directory (memory, in particular) always goes. Transfers over a slow
|
|
91
|
+
// link are where this matters — one old session can outweigh the rest.
|
|
92
|
+
const all = [];
|
|
93
|
+
for (const entry of await fs.readdir(source, { withFileTypes: true })) {
|
|
94
|
+
if (!entry.isFile() || !entry.name.endsWith(".jsonl"))
|
|
95
|
+
continue;
|
|
96
|
+
const stat = await fs.stat(path.join(source, entry.name));
|
|
97
|
+
all.push({ name: entry.name.replace(/\.jsonl$/, ""), size: stat.size, mtime: stat.mtime });
|
|
98
|
+
}
|
|
99
|
+
all.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
100
|
+
let sessions = all;
|
|
101
|
+
if (options.session?.length) {
|
|
102
|
+
sessions = all.filter((s) => options.session.some((want) => s.name.startsWith(want)));
|
|
103
|
+
const missing = options.session.filter((want) => !all.some((s) => s.name.startsWith(want)));
|
|
104
|
+
if (missing.length) {
|
|
105
|
+
console.error(`No session matching: ${missing.join(", ")}`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else if (options.latest) {
|
|
110
|
+
const n = parseInt(options.latest, 10);
|
|
111
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
112
|
+
console.error("--latest needs a positive number");
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
sessions = all.slice(0, n);
|
|
116
|
+
}
|
|
117
|
+
const selected = new Set(sessions.map((s) => s.name));
|
|
118
|
+
const allIds = new Set(all.map((s) => s.name));
|
|
119
|
+
const titles = new Map();
|
|
120
|
+
const staging = await fs.mkdtemp(path.join(os.tmpdir(), `dockai-sessions-${slug}-`));
|
|
121
|
+
try {
|
|
122
|
+
let files = 0;
|
|
123
|
+
let bytes = 0;
|
|
124
|
+
let rewritten = 0;
|
|
125
|
+
async function stage(srcDir, dstDir) {
|
|
126
|
+
await fs.mkdir(dstDir, { recursive: true });
|
|
127
|
+
for (const entry of await fs.readdir(srcDir, { withFileTypes: true })) {
|
|
128
|
+
if (entry.name === ".DS_Store")
|
|
129
|
+
continue;
|
|
130
|
+
if (srcDir === source) {
|
|
131
|
+
const id = entry.name.replace(/\.jsonl$/, "");
|
|
132
|
+
if (allIds.has(id) && !selected.has(id))
|
|
133
|
+
continue; // an unselected session, transcript or folder
|
|
134
|
+
}
|
|
135
|
+
const src = path.join(srcDir, entry.name);
|
|
136
|
+
const dst = path.join(dstDir, entry.name);
|
|
137
|
+
if (entry.isDirectory()) {
|
|
138
|
+
await stage(src, dst);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (!entry.isFile())
|
|
142
|
+
continue; // symlinks and the like have no place on the worker
|
|
143
|
+
const stat = await fs.stat(src);
|
|
144
|
+
if (TEXT_EXTENSIONS.has(path.extname(entry.name))) {
|
|
145
|
+
let text = await fs.readFile(src, "utf8");
|
|
146
|
+
for (const [re, to] of rewrites) {
|
|
147
|
+
text = text.replace(re, () => { rewritten += 1; return to; });
|
|
148
|
+
}
|
|
149
|
+
if (srcDir === source && entry.name.endsWith(".jsonl")) {
|
|
150
|
+
// A renamed session resumes by its name, not its id.
|
|
151
|
+
const m = [...text.matchAll(/"customTitle":"((?:[^"\\]|\\.)*)"/g)].pop();
|
|
152
|
+
if (m)
|
|
153
|
+
titles.set(entry.name.replace(/\.jsonl$/, ""), JSON.parse(`"${m[1]}"`));
|
|
154
|
+
}
|
|
155
|
+
await fs.writeFile(dst, text);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
await fs.copyFile(src, dst);
|
|
159
|
+
}
|
|
160
|
+
// Claude orders sessions by modification time; keep the real one.
|
|
161
|
+
await fs.utimes(dst, stat.atime, stat.mtime);
|
|
162
|
+
files += 1;
|
|
163
|
+
bytes += stat.size;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
await stage(source, staging);
|
|
167
|
+
console.log(`${sessions.length} of ${all.length} session${all.length === 1 ? "" : "s"} in ${source}` +
|
|
168
|
+
(sessions.length < all.length ? " (selected)" : ""));
|
|
169
|
+
for (const s of sessions) {
|
|
170
|
+
console.log(` ${s.name} ${(s.size / 1024 / 1024).toFixed(1)} MB ${s.mtime.toISOString().slice(0, 16).replace("T", " ")}`);
|
|
171
|
+
}
|
|
172
|
+
console.log(`${files} files, ${(bytes / 1024 / 1024).toFixed(0)} MB; ` +
|
|
173
|
+
`${rewritten} path references rewritten ${localProject} → ${remoteWorkspace}`);
|
|
174
|
+
if (options.dryRun) {
|
|
175
|
+
console.log(`Dry run — nothing sent. Destination: ${slug}:~/${remoteSessionDir}`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
await assertRunning(slug);
|
|
179
|
+
console.log(`Pushing to ${slug}:~/${remoteSessionDir}${options.overwrite ? " (overwriting)" : ""}…`);
|
|
180
|
+
const code = await pushTree(slug, {
|
|
181
|
+
cwd: staging,
|
|
182
|
+
paths: ["."],
|
|
183
|
+
remoteDir: remoteSessionDir,
|
|
184
|
+
overwrite: Boolean(options.overwrite),
|
|
185
|
+
});
|
|
186
|
+
if (code === 255) {
|
|
187
|
+
explainSshFailure(slug);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
if (code !== 0) {
|
|
191
|
+
console.error(`Push failed (exit ${code}).`);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
if (!options.remote) {
|
|
195
|
+
console.log("Done. Pick one up with:");
|
|
196
|
+
console.log(` dockai claude ${slug} -- -r`);
|
|
197
|
+
console.log(" (or --remote to resume them with Remote Control, so they appear in the Claude app)");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
// Put each pushed session on the phone through DockAI, which records it
|
|
201
|
+
// so a worker restart brings it back. The worker reattaches through the
|
|
202
|
+
// RC server when the session was spawned by one and resumes it with
|
|
203
|
+
// --remote-control otherwise; either way the remote id is reused.
|
|
204
|
+
const api = createApiClient();
|
|
205
|
+
console.log("Resuming with Remote Control…");
|
|
206
|
+
for (const s of sessions) {
|
|
207
|
+
try {
|
|
208
|
+
await api.project.remoteSessionStart.mutate({ slug, sessionId: s.name });
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
console.error(` ${titles.get(s.name) ?? s.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Registration takes a few seconds; the id lands in the transcript.
|
|
215
|
+
await new Promise((r) => setTimeout(r, 20_000));
|
|
216
|
+
const { sessions: remote } = (await api.project.claudeSessions.query({ slug }));
|
|
217
|
+
for (const s of sessions) {
|
|
218
|
+
const r = remote.find((x) => x.id === s.name);
|
|
219
|
+
const label = titles.get(s.name) ?? s.name;
|
|
220
|
+
const url = r?.remoteId ? `https://claude.ai/code/session_${r.remoteId.replace(/^cse_/, "")}` : null;
|
|
221
|
+
console.log(` ${label}: ${url ?? "(not registered yet — check the project page in a moment)"}`);
|
|
222
|
+
}
|
|
223
|
+
console.log("They appear under Claude sessions on the project page, and come back after a restart.");
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
await fs.rm(staging, { recursive: true, force: true });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function workerSessions(slug) {
|
|
230
|
+
await assertRunning(slug);
|
|
231
|
+
const { sessions } = (await createApiClient().project.claudeSessions.query({ slug }));
|
|
232
|
+
return sessions;
|
|
233
|
+
}
|
|
234
|
+
function ago(ms) {
|
|
235
|
+
if (!ms)
|
|
236
|
+
return "";
|
|
237
|
+
const s = (Date.now() - ms) / 1000;
|
|
238
|
+
if (s < 3600)
|
|
239
|
+
return `${Math.max(1, Math.floor(s / 60))}m`;
|
|
240
|
+
if (s < 86400)
|
|
241
|
+
return `${Math.floor(s / 3600)}h`;
|
|
242
|
+
return `${Math.floor(s / 86400)}d`;
|
|
243
|
+
}
|
|
244
|
+
/** What is on the worker, as the project page's sessions card shows it. */
|
|
245
|
+
export async function sessionsListCommand(slug) {
|
|
246
|
+
const sessions = (await workerSessions(slug)).filter((s) => s.messageCount > 0 || s.remoteState);
|
|
247
|
+
if (!sessions.length) {
|
|
248
|
+
console.log("No conversations in this workspace yet.");
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
for (const s of sessions) {
|
|
252
|
+
const state = s.remoteState ? `live (${s.remoteState})` : s.remoteId ? "registered" : "local";
|
|
253
|
+
console.log(`${s.id.slice(0, 8)} ${ago(s.updatedAt).padStart(3)} ${state.padEnd(18)} ${(s.title || s.firstPrompt || "").slice(0, 60)}`);
|
|
254
|
+
}
|
|
255
|
+
console.log(`\nResume one: dockai sessions resume ${slug} <id|name> (add --remote for the phone)`);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Hand a conversation off to a new, environment-bound one.
|
|
259
|
+
*
|
|
260
|
+
* The worker writes the dialogue to `.dockai/handoff/<file>.md`, creates the
|
|
261
|
+
* new conversation in the project's environment, and returns the one message
|
|
262
|
+
* to send in it. There is no supported way to move history into a bound
|
|
263
|
+
* session, so this does what the CLI's own fork does — carry the recent
|
|
264
|
+
* turns — through the workspace. The old conversation is left as it is.
|
|
265
|
+
*/
|
|
266
|
+
export async function sessionsHandoffCommand(slug, ref, options) {
|
|
267
|
+
const sessions = await workerSessions(slug);
|
|
268
|
+
const match = sessions.find((s) => s.id === ref) ?? sessions.find((s) => s.id.startsWith(ref)) ?? sessions.find((s) => s.title === ref);
|
|
269
|
+
if (!match) {
|
|
270
|
+
console.error(`No conversation matching "${ref}". See: dockai sessions list ${slug}`);
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
const turns = options.turns !== undefined ? Number(options.turns) : undefined;
|
|
274
|
+
if (turns !== undefined && (!Number.isInteger(turns) || turns < 1 || turns > 5000)) {
|
|
275
|
+
console.error("--turns must be a whole number between 1 and 5000");
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
const api = createApiClient();
|
|
279
|
+
const r = await api.project.sessionHandoff.mutate({
|
|
280
|
+
slug,
|
|
281
|
+
sessionId: match.id,
|
|
282
|
+
...(turns !== undefined ? { turns } : {}),
|
|
283
|
+
...(options.title ? { title: options.title } : {}),
|
|
284
|
+
...(options.fileOnly || (options.into && !options.terminal) ? { createSession: false } : {}),
|
|
285
|
+
...(options.into ? { into: options.into } : {}),
|
|
286
|
+
...(options.terminal ? { target: "terminal" } : {}),
|
|
287
|
+
});
|
|
288
|
+
if (r.terminalSessionId)
|
|
289
|
+
console.log(`Handed off "${match.title || match.id}" → interactive conversation "${r.title}" (${r.terminalSessionId.slice(0, 8)}) — on your phone in seconds; join it with: dockai claude ${slug}`);
|
|
290
|
+
else
|
|
291
|
+
console.log(options.fileOnly || options.into ? `Hand-off file written for "${match.title || match.id}"` : `Handed off "${match.title || match.id}" → "${r.title}"`);
|
|
292
|
+
console.log(` dialogue: ${r.relativeFile} (${r.turns} of ${r.totalTurns} turns)`);
|
|
293
|
+
if (r.url)
|
|
294
|
+
console.log(` open: ${r.url}`);
|
|
295
|
+
if (r.autoPickup) {
|
|
296
|
+
console.log(`\nThe conversation will read the hand-off on its own when it next starts — open it and say anything.`);
|
|
297
|
+
console.log(`If it does not, send this:\n\n ${r.message}\n`);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
const why = r.pickupReason === "agents_md" ? " (this project relies on AGENTS.md, so no start-up note was written)" : r.pickupReason === "target_busy" ? " (the conversation is busy right now)" : "";
|
|
301
|
+
console.log(`\nSend this as the first message in the conversation${why}:\n\n ${r.message}\n`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Resume a conversation by id, id prefix or title.
|
|
306
|
+
*
|
|
307
|
+
* In a terminal this is `dockai claude <slug> --resume <ref>`; with --remote
|
|
308
|
+
* the worker registers it for Remote Control through DockAI, which also
|
|
309
|
+
* records it so a restart brings it back.
|
|
310
|
+
*/
|
|
311
|
+
export async function sessionsResumeCommand(slug, ref, options) {
|
|
312
|
+
const sessions = await workerSessions(slug);
|
|
313
|
+
const match = sessions.find((s) => s.id === ref) ?? sessions.find((s) => s.id.startsWith(ref)) ?? sessions.find((s) => s.title === ref);
|
|
314
|
+
if (!match) {
|
|
315
|
+
console.error(`No conversation matching "${ref}". See: dockai sessions list ${slug}`);
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
if (!options.remote) {
|
|
319
|
+
// A renamed session resumes by name, not id.
|
|
320
|
+
await claudeCommand(slug, { session: `claude-${match.id.slice(0, 8)}` }, ["--resume", match.title || match.id]);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const api = createApiClient();
|
|
324
|
+
await api.project.remoteSessionStart.mutate({ slug, sessionId: match.id });
|
|
325
|
+
await new Promise((r) => setTimeout(r, 20_000));
|
|
326
|
+
const after = (await workerSessions(slug)).find((s) => s.id === match.id);
|
|
327
|
+
const url = after?.remoteId ? `https://claude.ai/code/session_${after.remoteId.replace(/^cse_/, "")}` : null;
|
|
328
|
+
console.log(`${match.title || match.id}: ${url ?? "registering — check the project page in a moment"}`);
|
|
329
|
+
}
|
|
330
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/commands/sessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { createApiClient } from "../api.js";
|
|
4
|
+
const BEGIN = "# ─── BEGIN DockAI ───";
|
|
5
|
+
const END = "# ─── END DockAI ───";
|
|
6
|
+
/** Quote a path for /bin/sh only when it contains something that needs it. */
|
|
7
|
+
function shQuote(path) {
|
|
8
|
+
if (/^[\w@%+=:,./-]+$/.test(path))
|
|
9
|
+
return path;
|
|
10
|
+
return `"${path.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* An absolute command that re-invokes this CLI.
|
|
14
|
+
*
|
|
15
|
+
* ssh runs ProxyCommand through /bin/sh, inheriting ssh's own environment —
|
|
16
|
+
* so a bare `dockai proxy` resolves fine from a terminal, where your shell
|
|
17
|
+
* PATH is present. It breaks when something else spawns ssh: GUI apps on
|
|
18
|
+
* macOS are launched by launchd with roughly `/usr/bin:/bin:/usr/sbin:/sbin`
|
|
19
|
+
* and never see a shell PATH. A dockai installed by nvm, fnm, volta, or
|
|
20
|
+
* Homebrew on Apple Silicon is nowhere on that list, so VS Code Remote-SSH
|
|
21
|
+
* and the Claude app's SSH backend fail with nothing more diagnostic than a
|
|
22
|
+
* closed connection — while the same host works by hand, which is the most
|
|
23
|
+
* annoying way for this to break.
|
|
24
|
+
*
|
|
25
|
+
* Writing absolute paths costs nothing and removes the whole failure class.
|
|
26
|
+
*/
|
|
27
|
+
function selfCommand() {
|
|
28
|
+
const script = process.argv[1];
|
|
29
|
+
// A single-file native build has no script argument — execPath is the CLI.
|
|
30
|
+
if (!script || !existsSync(script))
|
|
31
|
+
return shQuote(process.execPath);
|
|
32
|
+
return `${shQuote(process.execPath)} ${shQuote(script)}`;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Emit an ssh config block routing DockAI hosts through the tunnel.
|
|
36
|
+
*
|
|
37
|
+
* Once this is in ~/.ssh/config, `ssh dockai-<slug>` works from any network,
|
|
38
|
+
* and so does VS Code Remote-SSH — it just reads the same file. No VPN, and
|
|
39
|
+
* no SSH port published on the Docker host.
|
|
40
|
+
*/
|
|
41
|
+
export async function sshConfigCommand(options) {
|
|
42
|
+
const api = createApiClient();
|
|
43
|
+
const projects = (await api.project.list.query());
|
|
44
|
+
const block = [
|
|
45
|
+
BEGIN,
|
|
46
|
+
"# Generated by `dockai ssh-config`. Edit above or below, not inside.",
|
|
47
|
+
"# Re-run it after moving or reinstalling the dockai CLI — the",
|
|
48
|
+
"# ProxyCommand below is an absolute path to this install.",
|
|
49
|
+
"Host dockai-*",
|
|
50
|
+
" User claude",
|
|
51
|
+
` ProxyCommand ${selfCommand()} proxy %n`,
|
|
52
|
+
" # The tunnel is already authenticated; skip host key churn from",
|
|
53
|
+
" # containers that are recreated on every project start.",
|
|
54
|
+
" StrictHostKeyChecking no",
|
|
55
|
+
" UserKnownHostsFile /dev/null",
|
|
56
|
+
" LogLevel ERROR",
|
|
57
|
+
"",
|
|
58
|
+
...projects.flatMap((p) => [`# ${p.name}`, `Host dockai-${p.slug}`, ""]),
|
|
59
|
+
END,
|
|
60
|
+
].join("\n");
|
|
61
|
+
if (options.print) {
|
|
62
|
+
process.stdout.write(block + "\n");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
console.log(block);
|
|
66
|
+
console.log("");
|
|
67
|
+
console.log("Add it to your ssh config with:");
|
|
68
|
+
console.log(" dockai ssh-config --print >> ~/.ssh/config");
|
|
69
|
+
console.log("");
|
|
70
|
+
console.log("Then connect from anywhere:");
|
|
71
|
+
for (const p of projects.slice(0, 3)) {
|
|
72
|
+
console.log(` ssh dockai-${p.slug}`);
|
|
73
|
+
}
|
|
74
|
+
console.log(" code --remote ssh-remote+dockai-<slug> /workspace/<slug>");
|
|
75
|
+
console.log("");
|
|
76
|
+
console.log("Any tool that reads ~/.ssh/config can use these hosts —");
|
|
77
|
+
console.log("including the Claude app: pick SSH → Add SSH host → dockai-<slug>.");
|
|
78
|
+
}
|
|
79
|
+
/** Open a project in local VS Code over the tunnel. */
|
|
80
|
+
export async function codeCommand(slug) {
|
|
81
|
+
const host = `dockai-${slug}`;
|
|
82
|
+
const remotePath = `/workspace/${slug}`;
|
|
83
|
+
console.log(`Opening ${remotePath} on ${host}...`);
|
|
84
|
+
const child = spawn("code", ["--remote", `ssh-remote+${host}`, remotePath], {
|
|
85
|
+
stdio: "inherit",
|
|
86
|
+
});
|
|
87
|
+
child.on("error", () => {
|
|
88
|
+
console.error("Could not run `code`. Install the VS Code shell command:");
|
|
89
|
+
console.error(" VS Code → Command Palette → 'Shell Command: Install code command in PATH'");
|
|
90
|
+
console.error("");
|
|
91
|
+
console.error("Or open it manually:");
|
|
92
|
+
console.error(` ssh ${host}`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
});
|
|
95
|
+
child.on("close", (code) => {
|
|
96
|
+
if (code && code !== 0) {
|
|
97
|
+
console.error("");
|
|
98
|
+
console.error(`VS Code exited with ${code}. Is the ssh config block installed?`);
|
|
99
|
+
console.error(" dockai ssh-config --print >> ~/.ssh/config");
|
|
100
|
+
process.exit(code);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=ssh-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssh-config.js","sourceRoot":"","sources":["../../src/commands/ssh-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createApiClient } from "../api.js";
|
|
2
|
+
async function findProject(api, slug) {
|
|
3
|
+
const projects = (await api.project.list.query());
|
|
4
|
+
const project = projects.find((p) => p.slug === slug);
|
|
5
|
+
if (!project) {
|
|
6
|
+
console.error(`Project "${slug}" not found.`);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
return project;
|
|
10
|
+
}
|
|
11
|
+
export async function startCommand(slug) {
|
|
12
|
+
const api = createApiClient();
|
|
13
|
+
const project = await findProject(api, slug);
|
|
14
|
+
try {
|
|
15
|
+
await api.project.start.mutate({ id: project.id });
|
|
16
|
+
console.log(`Starting ${project.name}...`);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
20
|
+
console.error("Failed to start:", msg);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function stopCommand(slug) {
|
|
25
|
+
const api = createApiClient();
|
|
26
|
+
const project = await findProject(api, slug);
|
|
27
|
+
try {
|
|
28
|
+
await api.project.stop.mutate({ id: project.id });
|
|
29
|
+
console.log(`Stopping ${project.name}...`);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
33
|
+
console.error("Failed to stop:", msg);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=start-stop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start-stop.js","sourceRoot":"","sources":["../../src/commands/start-stop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAgC,MAAM,WAAW,CAAC;AAE1E,KAAK,UAAU,WAAW,CAAC,GAAc,EAAE,IAAY;IACrD,MAAM,QAAQ,GAAG,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAc,CAAC;IAC/D,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACtD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,YAAY,IAAI,cAAc,CAAC,CAAC;QAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY;IAC7C,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAE7C,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,YAAY,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAE7C,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QAClD,OAAO,CAAC,GAAG,CAAC,YAAY,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;QACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createApiClient } from "../api.js";
|
|
2
|
+
import { getServerUrl, getToken } from "../config.js";
|
|
3
|
+
export async function statusCommand() {
|
|
4
|
+
const serverUrl = getServerUrl();
|
|
5
|
+
const token = getToken();
|
|
6
|
+
const api = createApiClient();
|
|
7
|
+
try {
|
|
8
|
+
const res = await fetch(`${serverUrl}/api/health`);
|
|
9
|
+
const data = (await res.json());
|
|
10
|
+
console.log(`\nServer: ${serverUrl}`);
|
|
11
|
+
console.log(`Status: \x1b[32m${data.status}\x1b[0m`);
|
|
12
|
+
console.log(`Time: ${data.timestamp}`);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
console.log(`\nServer: ${serverUrl}`);
|
|
16
|
+
console.log(`Status: \x1b[31munreachable\x1b[0m\n`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!token) {
|
|
20
|
+
console.log("\nNot logged in. Run 'dockai login' first.\n");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const projects = (await api.project.list.query());
|
|
25
|
+
const running = projects.filter((p) => p.status === "RUNNING").length;
|
|
26
|
+
console.log(`\nProjects: ${projects.length} total, ${running} running\n`);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
30
|
+
if (msg.includes("UNAUTHORIZED") || msg.includes("401")) {
|
|
31
|
+
console.log("\nToken rejected. Run 'dockai login' again.\n");
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
console.log(`\nCould not fetch projects: ${msg}\n`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAgB,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEtD,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAE9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,SAAS,aAAa,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA0C,CAAC;QACzE,OAAO,CAAC,GAAG,CAAC,aAAa,SAAS,EAAE,CAAC,CAAC;QACtC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,CAAC,MAAM,SAAS,CAAC,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,aAAa,SAAS,EAAE,CAAC,CAAC;QACtC,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;QACpD,OAAO;IACT,CAAC;IAED,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;QAC5D,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAc,CAAC;QAC/D,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;QACtE,OAAO,CAAC,GAAG,CAAC,eAAe,QAAQ,CAAC,MAAM,WAAW,OAAO,YAAY,CAAC,CAAC;IAC5E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,IAAI,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACxD,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;QAC/D,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,+BAA+B,GAAG,IAAI,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createApiClient } from "../api.js";
|
|
2
|
+
import { getServerUrl, getToken } from "../config.js";
|
|
3
|
+
export async function whoamiCommand() {
|
|
4
|
+
const serverUrl = getServerUrl();
|
|
5
|
+
const token = getToken();
|
|
6
|
+
if (!token) {
|
|
7
|
+
console.error("Not logged in. Run 'dockai login' first.");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
const api = createApiClient();
|
|
11
|
+
try {
|
|
12
|
+
const me = (await api.users.me.query());
|
|
13
|
+
console.log(`${me.email} (${me.name})`);
|
|
14
|
+
console.log(`Server: ${serverUrl}`);
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
18
|
+
if (msg.includes("UNAUTHORIZED") || msg.includes("401")) {
|
|
19
|
+
console.error("Token rejected. Run 'dockai login' again.");
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
console.error(`Failed to identify: ${msg}`);
|
|
23
|
+
}
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=whoami.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAuB,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEtD,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IAEzB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAmB,CAAC;QAC1D,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,WAAW,SAAS,EAAE,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,IAAI,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACxD,OAAO,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAC7D,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,uBAAuB,GAAG,EAAE,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const CONFIG_DIR = path.join(homedir(), ".config", "dockai");
|
|
5
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
6
|
+
const DEFAULT_CONFIG = {
|
|
7
|
+
serverUrl: "http://localhost:3000",
|
|
8
|
+
token: null,
|
|
9
|
+
};
|
|
10
|
+
export function getConfig() {
|
|
11
|
+
try {
|
|
12
|
+
if (existsSync(CONFIG_FILE)) {
|
|
13
|
+
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
14
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// ignore
|
|
19
|
+
}
|
|
20
|
+
return { ...DEFAULT_CONFIG };
|
|
21
|
+
}
|
|
22
|
+
export function saveConfig(config) {
|
|
23
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
24
|
+
const existing = getConfig();
|
|
25
|
+
const merged = { ...existing, ...config };
|
|
26
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n");
|
|
27
|
+
}
|
|
28
|
+
export function getServerUrl() {
|
|
29
|
+
return getConfig().serverUrl;
|
|
30
|
+
}
|
|
31
|
+
export function getToken() {
|
|
32
|
+
return getConfig().token;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAO7B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAC7D,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEzD,MAAM,cAAc,GAAW;IAC7B,SAAS,EAAE,uBAAuB;IAClC,KAAK,EAAE,IAAI;CACZ,CAAC;AAEF,MAAM,UAAU,SAAS;IACvB,IAAI,CAAC;QACH,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;YAC/C,OAAO,EAAE,GAAG,cAAc,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACnD,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;IACD,OAAO,EAAE,GAAG,cAAc,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAuB;IAChD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,SAAS,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAC;IAC1C,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,SAAS,EAAE,CAAC,SAAS,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,QAAQ;IACtB,OAAO,SAAS,EAAE,CAAC,KAAK,CAAC;AAC3B,CAAC"}
|