@shogo-ai/cli 1.4.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/LICENSE +21 -0
- package/README.md +18 -0
- package/dist/chunk-UB4RE2UC.js +427 -0
- package/dist/chunk-YYO73V5H.js +227 -0
- package/dist/deploy.cjs +255 -0
- package/dist/deploy.d.cts +168 -0
- package/dist/deploy.d.ts +168 -0
- package/dist/deploy.js +15 -0
- package/dist/index.cjs +688 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +31 -0
- package/dist/pkg.cjs +457 -0
- package/dist/pkg.d.cts +139 -0
- package/dist/pkg.d.ts +139 -0
- package/dist/pkg.js +19 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shogo Technologies, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: MIT -->
|
|
2
|
+
<!-- Copyright (C) 2026 Shogo Technologies, Inc. -->
|
|
3
|
+
|
|
4
|
+
# @shogo-ai/cli
|
|
5
|
+
|
|
6
|
+
CLI helpers consumed by the `shogo` binary (still shipped via
|
|
7
|
+
`@shogo-ai/sdk`'s `bin` field). Most users won't import this package
|
|
8
|
+
directly; it exists so the deploy/manifest/packager logic can evolve
|
|
9
|
+
independently from the SDK release cadence.
|
|
10
|
+
|
|
11
|
+
| Subpath | Use |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `@shogo-ai/cli/deploy` | `validateManifest`, `runDeploy`, manifest types. |
|
|
14
|
+
| `@shogo-ai/cli/pkg` | `pkg(...)` — package manager detection and dispatch (npm/bun/pnpm/yarn). |
|
|
15
|
+
|
|
16
|
+
## License
|
|
17
|
+
|
|
18
|
+
MIT — see [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
// src/pkg.ts
|
|
2
|
+
import { execSync as nodeExecSync, spawn } from "child_process";
|
|
3
|
+
import { existsSync, readlinkSync, lstatSync } from "fs";
|
|
4
|
+
import { isAbsolute, join, resolve as resolvePath, dirname } from "path";
|
|
5
|
+
var NodeMissingError = class extends Error {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(
|
|
8
|
+
"Shogo Desktop on Windows requires Node.js 20+ to run project sandboxes. npm.cmd was not found on PATH and is not present at C:\\Program Files\\nodejs\\. Install Node.js from https://nodejs.org/ (LTS recommended), then restart Shogo."
|
|
9
|
+
);
|
|
10
|
+
this.code = "NODE_NOT_INSTALLED";
|
|
11
|
+
this.name = "NodeMissingError";
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var WINDOWS_NODE_DIR = "C:\\Program Files\\nodejs";
|
|
15
|
+
function isNodeAvailableOnWindows(pathEnv = process.env.PATH) {
|
|
16
|
+
if (process.platform !== "win32") return true;
|
|
17
|
+
if (existsSync(join(WINDOWS_NODE_DIR, "npm.cmd"))) return true;
|
|
18
|
+
if (!pathEnv) return false;
|
|
19
|
+
for (const dir of pathEnv.split(";")) {
|
|
20
|
+
if (dir && existsSync(join(dir, "npm.cmd"))) return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
var _unixNodeAvailable = null;
|
|
25
|
+
function isNodeAvailableOnUnix(pathEnv = process.env.PATH) {
|
|
26
|
+
if (process.platform === "win32") return false;
|
|
27
|
+
if (pathEnv === process.env.PATH && _unixNodeAvailable !== null) return _unixNodeAvailable;
|
|
28
|
+
let found = false;
|
|
29
|
+
if (pathEnv) {
|
|
30
|
+
for (const dir of pathEnv.split(":")) {
|
|
31
|
+
if (!dir) continue;
|
|
32
|
+
if (existsSync(join(dir, "node"))) {
|
|
33
|
+
found = true;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (pathEnv === process.env.PATH) _unixNodeAvailable = found;
|
|
39
|
+
return found;
|
|
40
|
+
}
|
|
41
|
+
function _resetUnixNodeCache() {
|
|
42
|
+
_unixNodeAvailable = null;
|
|
43
|
+
}
|
|
44
|
+
function resolveBinInvocation(workspaceDir, binName) {
|
|
45
|
+
const binDir = join(workspaceDir, "node_modules", ".bin");
|
|
46
|
+
const isWindows = process.platform === "win32";
|
|
47
|
+
const candidates = isWindows ? [join(binDir, `${binName}.CMD`), join(binDir, `${binName}.cmd`), join(binDir, `${binName}.exe`)] : [join(binDir, binName)];
|
|
48
|
+
const shim = candidates.find((p) => existsSync(p));
|
|
49
|
+
if (!shim) return null;
|
|
50
|
+
if (isWindows) return { cmd: shim, argsPrefix: [] };
|
|
51
|
+
if (isNodeAvailableOnUnix()) return { cmd: shim, argsPrefix: [] };
|
|
52
|
+
try {
|
|
53
|
+
const st = lstatSync(shim);
|
|
54
|
+
if (!st.isSymbolicLink()) return { cmd: shim, argsPrefix: [] };
|
|
55
|
+
const target = readlinkSync(shim);
|
|
56
|
+
const jsEntry = isAbsolute(target) ? target : resolvePath(dirname(shim), target);
|
|
57
|
+
if (!existsSync(jsEntry)) return { cmd: shim, argsPrefix: [] };
|
|
58
|
+
const bun = process.env.SHOGO_BUN_PATH || "bun";
|
|
59
|
+
return { cmd: bun, argsPrefix: [jsEntry] };
|
|
60
|
+
} catch {
|
|
61
|
+
return { cmd: shim, argsPrefix: [] };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
var IS_WINDOWS = process.platform === "win32";
|
|
65
|
+
var DEFAULT_INSTALL_TIMEOUT = parseInt(
|
|
66
|
+
process.env.SHOGO_INSTALL_TIMEOUT_MS || (IS_WINDOWS ? "300000" : "300000"),
|
|
67
|
+
10
|
|
68
|
+
);
|
|
69
|
+
var DEFAULT_EXEC_TIMEOUT = 6e4;
|
|
70
|
+
var PlatformPackageManager = class {
|
|
71
|
+
constructor() {
|
|
72
|
+
this.isWindows = IS_WINDOWS;
|
|
73
|
+
}
|
|
74
|
+
/** Resolved path to the bun binary — prefers SHOGO_BUN_PATH (set by desktop app) over bare `bun`. */
|
|
75
|
+
get bunBinary() {
|
|
76
|
+
return process.env.SHOGO_BUN_PATH || "bun";
|
|
77
|
+
}
|
|
78
|
+
shellOpt() {
|
|
79
|
+
return IS_WINDOWS ? true : void 0;
|
|
80
|
+
}
|
|
81
|
+
spawnEnv(base) {
|
|
82
|
+
const source = base ?? process.env;
|
|
83
|
+
const env = {};
|
|
84
|
+
let pathValue = "";
|
|
85
|
+
for (const [k, v] of Object.entries(source)) {
|
|
86
|
+
if (v === void 0) continue;
|
|
87
|
+
if (IS_WINDOWS && k.toLowerCase() === "path") {
|
|
88
|
+
if (v) pathValue = v;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
env[k] = v;
|
|
92
|
+
}
|
|
93
|
+
if (IS_WINDOWS) {
|
|
94
|
+
const nodePath = "C:\\Program Files\\nodejs";
|
|
95
|
+
env.PATH = pathValue.includes(nodePath) ? pathValue : `${nodePath};${pathValue}`;
|
|
96
|
+
}
|
|
97
|
+
return env;
|
|
98
|
+
}
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Install — synchronous
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
installSync(cwd, opts) {
|
|
103
|
+
const timeout = opts?.timeout ?? DEFAULT_INSTALL_TIMEOUT;
|
|
104
|
+
const stdio = opts?.stdio ?? "pipe";
|
|
105
|
+
const env = this.spawnEnv(opts?.env);
|
|
106
|
+
if (IS_WINDOWS) {
|
|
107
|
+
if (isNodeAvailableOnWindows(env.PATH)) {
|
|
108
|
+
try {
|
|
109
|
+
nodeExecSync("npm.cmd install --loglevel=error", {
|
|
110
|
+
cwd,
|
|
111
|
+
timeout,
|
|
112
|
+
stdio,
|
|
113
|
+
env,
|
|
114
|
+
shell: "cmd.exe"
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
} catch (err) {
|
|
118
|
+
const wrapped = wrapWindowsNpmError(err);
|
|
119
|
+
if (!wrapped) throw err;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
this.installSyncBunCopyfile(cwd, opts);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const bun = this.bunBinary;
|
|
126
|
+
if (opts?.frozen) {
|
|
127
|
+
try {
|
|
128
|
+
nodeExecSync(`"${bun}" install --frozen-lockfile 2>&1`, { cwd, timeout, stdio, env });
|
|
129
|
+
return;
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
nodeExecSync(`"${bun}" install`, { cwd, timeout, stdio, env });
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Windows-only fallback path: `bun install [--frozen-lockfile]
|
|
137
|
+
* --backend=copyfile`. Used when npm.cmd isn't available on PATH
|
|
138
|
+
* (typically: Shogo Desktop bundles bun but the user didn't install
|
|
139
|
+
* Node.js separately).
|
|
140
|
+
*
|
|
141
|
+
* The `--backend=copyfile` flag forces bun to copy package contents
|
|
142
|
+
* into `node_modules/` instead of hardlinking from
|
|
143
|
+
* `~/.bun/install/cache`. Hardlinks are bun's default for speed but
|
|
144
|
+
* fail in confusing ways on Windows (the destination directory gets
|
|
145
|
+
* created but ends up empty, and downstream `require('webidl-conversions')`
|
|
146
|
+
* etc. then crash with MODULE_NOT_FOUND). See bun#10327 / #28653.
|
|
147
|
+
*/
|
|
148
|
+
installSyncBunCopyfile(cwd, opts) {
|
|
149
|
+
const timeout = opts?.timeout ?? DEFAULT_INSTALL_TIMEOUT;
|
|
150
|
+
const stdio = opts?.stdio ?? "pipe";
|
|
151
|
+
const env = this.spawnEnv(opts?.env);
|
|
152
|
+
const bun = this.bunBinary;
|
|
153
|
+
console.warn(
|
|
154
|
+
"[platform-pkg] Node.js / npm.cmd not found on PATH \u2014 falling back to `bun install --backend=copyfile` (Shogo Desktop bundle path). This is slower than hardlink but avoids the Windows extract bug."
|
|
155
|
+
);
|
|
156
|
+
if (opts?.frozen) {
|
|
157
|
+
try {
|
|
158
|
+
nodeExecSync(`"${bun}" install --frozen-lockfile --backend=copyfile`, {
|
|
159
|
+
cwd,
|
|
160
|
+
timeout,
|
|
161
|
+
stdio,
|
|
162
|
+
env
|
|
163
|
+
});
|
|
164
|
+
return;
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
nodeExecSync(`"${bun}" install --backend=copyfile`, { cwd, timeout, stdio, env });
|
|
169
|
+
}
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
// Install — async with spawn (used by RuntimeManager)
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
installAsync(cwd, opts) {
|
|
174
|
+
if (IS_WINDOWS) {
|
|
175
|
+
const env = this.spawnEnv(opts?.env);
|
|
176
|
+
if (isNodeAvailableOnWindows(env.PATH)) {
|
|
177
|
+
return this.installAsyncWindowsNpm(cwd, opts).catch((err) => {
|
|
178
|
+
if (err instanceof NodeMissingError) {
|
|
179
|
+
return this.installAsyncBunCopyfile(cwd, opts);
|
|
180
|
+
}
|
|
181
|
+
throw err;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return this.installAsyncBunCopyfile(cwd, opts);
|
|
185
|
+
}
|
|
186
|
+
return this.installAsyncBun(cwd, opts);
|
|
187
|
+
}
|
|
188
|
+
installAsyncWindowsNpm(cwd, opts) {
|
|
189
|
+
const timeout = opts?.timeout ?? DEFAULT_INSTALL_TIMEOUT;
|
|
190
|
+
const env = this.spawnEnv(opts?.env);
|
|
191
|
+
const cmd = "npm.cmd";
|
|
192
|
+
const args = ["install", "--loglevel=error"];
|
|
193
|
+
return new Promise((resolve, reject) => {
|
|
194
|
+
const proc = spawn(cmd, args, {
|
|
195
|
+
cwd,
|
|
196
|
+
stdio: "pipe",
|
|
197
|
+
env,
|
|
198
|
+
shell: true
|
|
199
|
+
});
|
|
200
|
+
const timer = setTimeout(() => {
|
|
201
|
+
proc.kill();
|
|
202
|
+
reject(new Error(`${cmd} install timed out after ${timeout / 1e3}s`));
|
|
203
|
+
}, timeout);
|
|
204
|
+
let stderr = "";
|
|
205
|
+
proc.stderr?.on("data", (d) => {
|
|
206
|
+
stderr += d.toString();
|
|
207
|
+
});
|
|
208
|
+
proc.on("exit", (code) => {
|
|
209
|
+
clearTimeout(timer);
|
|
210
|
+
if (code === 0) return resolve();
|
|
211
|
+
if (/not recognized as an internal or external command/i.test(stderr)) {
|
|
212
|
+
return reject(new NodeMissingError());
|
|
213
|
+
}
|
|
214
|
+
reject(new Error(`${cmd} install exited with code ${code}
|
|
215
|
+
${stderr}`));
|
|
216
|
+
});
|
|
217
|
+
proc.on("error", (err) => {
|
|
218
|
+
clearTimeout(timer);
|
|
219
|
+
if (err.code === "ENOENT") return reject(new NodeMissingError());
|
|
220
|
+
reject(err);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
installAsyncBunCopyfile(cwd, opts) {
|
|
225
|
+
console.warn(
|
|
226
|
+
"[platform-pkg] Node.js / npm.cmd not found on PATH \u2014 falling back to `bun install --backend=copyfile` (Shogo Desktop bundle path)."
|
|
227
|
+
);
|
|
228
|
+
return this.installAsyncBun(cwd, opts, ["--backend=copyfile"]);
|
|
229
|
+
}
|
|
230
|
+
installAsyncBun(cwd, opts, extraArgs = []) {
|
|
231
|
+
const timeout = opts?.timeout ?? DEFAULT_INSTALL_TIMEOUT;
|
|
232
|
+
const env = this.spawnEnv(opts?.env);
|
|
233
|
+
const bun = this.bunBinary;
|
|
234
|
+
const baseArgs = ["install", ...extraArgs];
|
|
235
|
+
const frozenArgs = opts?.frozen ? ["install", "--frozen-lockfile", ...extraArgs] : null;
|
|
236
|
+
const runOnce = (args) => new Promise((resolve, reject) => {
|
|
237
|
+
const proc = spawn(bun, args, {
|
|
238
|
+
cwd,
|
|
239
|
+
stdio: "pipe",
|
|
240
|
+
env
|
|
241
|
+
});
|
|
242
|
+
const timer = setTimeout(() => {
|
|
243
|
+
proc.kill();
|
|
244
|
+
reject(new Error(`bun ${args.join(" ")} timed out after ${timeout / 1e3}s`));
|
|
245
|
+
}, timeout);
|
|
246
|
+
let stderr = "";
|
|
247
|
+
proc.stderr?.on("data", (d) => {
|
|
248
|
+
stderr += d.toString();
|
|
249
|
+
});
|
|
250
|
+
proc.stdout?.on("data", () => {
|
|
251
|
+
});
|
|
252
|
+
proc.on("exit", (code) => {
|
|
253
|
+
clearTimeout(timer);
|
|
254
|
+
resolve({ ok: code === 0, stderr });
|
|
255
|
+
});
|
|
256
|
+
proc.on("error", (err) => {
|
|
257
|
+
clearTimeout(timer);
|
|
258
|
+
reject(err);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
const run = async () => {
|
|
262
|
+
if (frozenArgs) {
|
|
263
|
+
const first = await runOnce(frozenArgs);
|
|
264
|
+
if (first.ok) return;
|
|
265
|
+
}
|
|
266
|
+
const second = await runOnce(baseArgs);
|
|
267
|
+
if (!second.ok) {
|
|
268
|
+
throw new Error(`bun install failed: ${second.stderr}`);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
return run();
|
|
272
|
+
}
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// Exec — run an npx/bunx tool synchronously
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
execToolSync(tool, args, cwd, opts) {
|
|
277
|
+
const timeout = opts?.timeout ?? DEFAULT_EXEC_TIMEOUT;
|
|
278
|
+
const stdio = opts?.stdio ?? "pipe";
|
|
279
|
+
const env = this.spawnEnv(opts?.env);
|
|
280
|
+
const argStr = args.length > 0 ? ` ${args.join(" ")}` : "";
|
|
281
|
+
const bun = this.bunBinary;
|
|
282
|
+
const cmd = IS_WINDOWS ? `npx ${tool}${argStr}` : `"${bun}" x ${opts?.useBunFlag ? "--bun " : ""}${tool}${argStr}`;
|
|
283
|
+
return nodeExecSync(cmd, {
|
|
284
|
+
cwd,
|
|
285
|
+
timeout,
|
|
286
|
+
stdio,
|
|
287
|
+
env,
|
|
288
|
+
// shellOpt() returns `true` on Windows so Node/Bun pick the platform
|
|
289
|
+
// default shell from `process.env.ComSpec`. ExecSyncOptions only
|
|
290
|
+
// narrowly types `shell` as `string | undefined`, but the runtime
|
|
291
|
+
// accepts a boolean and the cross-platform behavior depends on it
|
|
292
|
+
// (see shellOpt() doc).
|
|
293
|
+
shell: this.shellOpt(),
|
|
294
|
+
encoding: "utf-8"
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
// Exec — async variant that DOES NOT block the JS event loop.
|
|
299
|
+
//
|
|
300
|
+
// The sync variant above is fine for one-shot project scaffolding
|
|
301
|
+
// (template.copy.ts) where blocking is intentional. Inside the runtime's
|
|
302
|
+
// hot path (PreviewManager.runPrismaIfNeeded → /pool/assign) it caused
|
|
303
|
+
// a measured ~4.7s freeze of /pool/assign in staging because prisma's
|
|
304
|
+
// child process held the event loop for the full duration. Always
|
|
305
|
+
// prefer this variant in long-lived services.
|
|
306
|
+
// ---------------------------------------------------------------------------
|
|
307
|
+
async execToolAsync(tool, args, cwd, opts) {
|
|
308
|
+
const timeout = opts?.timeout ?? DEFAULT_EXEC_TIMEOUT;
|
|
309
|
+
const stdio = opts?.stdio ?? "pipe";
|
|
310
|
+
const env = this.spawnEnv(opts?.env);
|
|
311
|
+
let cmd;
|
|
312
|
+
let argv;
|
|
313
|
+
let useShell;
|
|
314
|
+
if (IS_WINDOWS) {
|
|
315
|
+
const argStr = args.length > 0 ? ` ${args.join(" ")}` : "";
|
|
316
|
+
cmd = `npx ${tool}${argStr}`;
|
|
317
|
+
argv = [];
|
|
318
|
+
useShell = true;
|
|
319
|
+
} else {
|
|
320
|
+
cmd = this.bunBinary;
|
|
321
|
+
argv = ["x"];
|
|
322
|
+
if (opts?.useBunFlag) argv.push("--bun");
|
|
323
|
+
argv.push(tool, ...args);
|
|
324
|
+
useShell = false;
|
|
325
|
+
}
|
|
326
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
327
|
+
const proc = spawn(cmd, argv, {
|
|
328
|
+
cwd,
|
|
329
|
+
env,
|
|
330
|
+
stdio,
|
|
331
|
+
shell: useShell ? this.shellOpt() : false
|
|
332
|
+
});
|
|
333
|
+
let stdout = "";
|
|
334
|
+
let stderr = "";
|
|
335
|
+
proc.stdout?.on("data", (chunk) => {
|
|
336
|
+
stdout += chunk.toString();
|
|
337
|
+
});
|
|
338
|
+
proc.stderr?.on("data", (chunk) => {
|
|
339
|
+
stderr += chunk.toString();
|
|
340
|
+
});
|
|
341
|
+
const timer = setTimeout(() => {
|
|
342
|
+
try {
|
|
343
|
+
proc.kill("SIGKILL");
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
rejectPromise(
|
|
347
|
+
new Error(`${tool} ${args.join(" ")} timed out after ${timeout}ms`)
|
|
348
|
+
);
|
|
349
|
+
}, timeout);
|
|
350
|
+
proc.on("error", (err) => {
|
|
351
|
+
clearTimeout(timer);
|
|
352
|
+
rejectPromise(err);
|
|
353
|
+
});
|
|
354
|
+
proc.on("exit", (code) => {
|
|
355
|
+
clearTimeout(timer);
|
|
356
|
+
if (code === 0) {
|
|
357
|
+
resolvePromise(stdout);
|
|
358
|
+
} else {
|
|
359
|
+
const errMsg = stderr.trim() || stdout.trim() || `exit ${code}`;
|
|
360
|
+
rejectPromise(new Error(`${tool} ${args.join(" ")} failed: ${errMsg}`));
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
// ---------------------------------------------------------------------------
|
|
366
|
+
// Prisma convenience wrappers
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
prismaGenerate(cwd, opts) {
|
|
369
|
+
this.execToolSync("prisma", ["generate"], cwd, {
|
|
370
|
+
timeout: opts?.timeout ?? 3e4,
|
|
371
|
+
stdio: opts?.stdio,
|
|
372
|
+
env: opts?.env
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
prismaDbPush(cwd, opts) {
|
|
376
|
+
const args = ["db", "push"];
|
|
377
|
+
if (opts?.acceptDataLoss) args.push("--accept-data-loss");
|
|
378
|
+
this.execToolSync("prisma", args, cwd, {
|
|
379
|
+
timeout: opts?.timeout ?? DEFAULT_EXEC_TIMEOUT,
|
|
380
|
+
stdio: opts?.stdio,
|
|
381
|
+
env: opts?.env
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
// Async siblings of the prisma wrappers above. Use these from any
|
|
385
|
+
// long-running service (runtime, agent gateway, preview manager) so a
|
|
386
|
+
// 1-3s prisma child process doesn't freeze /health, /pool/assign, or
|
|
387
|
+
// other concurrent HTTP work. Same arg shape, same timeout defaults.
|
|
388
|
+
async prismaGenerateAsync(cwd, opts) {
|
|
389
|
+
await this.execToolAsync("prisma", ["generate"], cwd, {
|
|
390
|
+
timeout: opts?.timeout ?? 3e4,
|
|
391
|
+
stdio: opts?.stdio,
|
|
392
|
+
env: opts?.env
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
async prismaDbPushAsync(cwd, opts) {
|
|
396
|
+
const args = ["db", "push"];
|
|
397
|
+
if (opts?.acceptDataLoss) args.push("--accept-data-loss");
|
|
398
|
+
await this.execToolAsync("prisma", args, cwd, {
|
|
399
|
+
timeout: opts?.timeout ?? DEFAULT_EXEC_TIMEOUT,
|
|
400
|
+
stdio: opts?.stdio,
|
|
401
|
+
env: opts?.env
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
function wrapWindowsNpmError(err) {
|
|
406
|
+
if (!IS_WINDOWS) return null;
|
|
407
|
+
const blob = `${err?.stderr?.toString?.() ?? ""}
|
|
408
|
+
${err?.stdout?.toString?.() ?? ""}
|
|
409
|
+
${err?.message ?? ""}`;
|
|
410
|
+
if (/not recognized as an internal or external command/i.test(blob)) {
|
|
411
|
+
return new NodeMissingError();
|
|
412
|
+
}
|
|
413
|
+
if (err?.code === "ENOENT") return new NodeMissingError();
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
var pkg = new PlatformPackageManager();
|
|
417
|
+
|
|
418
|
+
export {
|
|
419
|
+
NodeMissingError,
|
|
420
|
+
isNodeAvailableOnWindows,
|
|
421
|
+
isNodeAvailableOnUnix,
|
|
422
|
+
_resetUnixNodeCache,
|
|
423
|
+
resolveBinInvocation,
|
|
424
|
+
PlatformPackageManager,
|
|
425
|
+
pkg
|
|
426
|
+
};
|
|
427
|
+
//# sourceMappingURL=chunk-UB4RE2UC.js.map
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// src/deploy.ts
|
|
2
|
+
var AGENT_NAME_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
|
|
3
|
+
var KNOWN_KEYS = /* @__PURE__ */ new Set([
|
|
4
|
+
"systemPrompt",
|
|
5
|
+
"tools",
|
|
6
|
+
"characterName",
|
|
7
|
+
"displayName",
|
|
8
|
+
"voiceId",
|
|
9
|
+
"firstMessage",
|
|
10
|
+
"model"
|
|
11
|
+
]);
|
|
12
|
+
function validateManifest(raw) {
|
|
13
|
+
const issues = [];
|
|
14
|
+
if (raw == null) return { agents: {}, issues };
|
|
15
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
16
|
+
issues.push({
|
|
17
|
+
path: "agents",
|
|
18
|
+
message: "`agents` must be an object keyed by agent name"
|
|
19
|
+
});
|
|
20
|
+
return { agents: {}, issues };
|
|
21
|
+
}
|
|
22
|
+
const agents = {};
|
|
23
|
+
for (const [name, value] of Object.entries(raw)) {
|
|
24
|
+
if (!AGENT_NAME_PATTERN.test(name)) {
|
|
25
|
+
issues.push({
|
|
26
|
+
path: `agents.${name}`,
|
|
27
|
+
message: `agent name must match ${AGENT_NAME_PATTERN}`
|
|
28
|
+
});
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
32
|
+
issues.push({
|
|
33
|
+
path: `agents.${name}`,
|
|
34
|
+
message: "value must be an object"
|
|
35
|
+
});
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const entry = {};
|
|
39
|
+
for (const [k, v] of Object.entries(value)) {
|
|
40
|
+
if (!KNOWN_KEYS.has(k)) {
|
|
41
|
+
issues.push({
|
|
42
|
+
path: `agents.${name}.${k}`,
|
|
43
|
+
message: `unknown field; supported: ${[...KNOWN_KEYS].join(", ")}`
|
|
44
|
+
});
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (k === "tools") {
|
|
48
|
+
if (!Array.isArray(v)) {
|
|
49
|
+
issues.push({
|
|
50
|
+
path: `agents.${name}.tools`,
|
|
51
|
+
message: "must be an array of strings or { name, description?, inputSchema? } objects"
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const tools = [];
|
|
56
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
57
|
+
let valid = true;
|
|
58
|
+
for (let i = 0; i < v.length; i++) {
|
|
59
|
+
const t = v[i];
|
|
60
|
+
if (typeof t === "string") {
|
|
61
|
+
if (t.length === 0) {
|
|
62
|
+
issues.push({
|
|
63
|
+
path: `agents.${name}.tools[${i}]`,
|
|
64
|
+
message: "tool name must be a non-empty string"
|
|
65
|
+
});
|
|
66
|
+
valid = false;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (seenNames.has(t)) {
|
|
70
|
+
issues.push({
|
|
71
|
+
path: `agents.${name}.tools[${i}]`,
|
|
72
|
+
message: `duplicate tool name '${t}'`
|
|
73
|
+
});
|
|
74
|
+
valid = false;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
seenNames.add(t);
|
|
78
|
+
tools.push(t);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (t == null || typeof t !== "object" || Array.isArray(t)) {
|
|
82
|
+
issues.push({
|
|
83
|
+
path: `agents.${name}.tools[${i}]`,
|
|
84
|
+
message: "tools entries must be a string or { name, description?, inputSchema? }"
|
|
85
|
+
});
|
|
86
|
+
valid = false;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const obj = t;
|
|
90
|
+
if (typeof obj.name !== "string" || obj.name.length === 0) {
|
|
91
|
+
issues.push({
|
|
92
|
+
path: `agents.${name}.tools[${i}].name`,
|
|
93
|
+
message: "name is required and must be a non-empty string"
|
|
94
|
+
});
|
|
95
|
+
valid = false;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (seenNames.has(obj.name)) {
|
|
99
|
+
issues.push({
|
|
100
|
+
path: `agents.${name}.tools[${i}]`,
|
|
101
|
+
message: `duplicate tool name '${obj.name}'`
|
|
102
|
+
});
|
|
103
|
+
valid = false;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (obj.description !== void 0 && typeof obj.description !== "string") {
|
|
107
|
+
issues.push({
|
|
108
|
+
path: `agents.${name}.tools[${i}].description`,
|
|
109
|
+
message: "description must be a string"
|
|
110
|
+
});
|
|
111
|
+
valid = false;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (obj.inputSchema !== void 0 && (obj.inputSchema === null || typeof obj.inputSchema !== "object" || Array.isArray(obj.inputSchema))) {
|
|
115
|
+
issues.push({
|
|
116
|
+
path: `agents.${name}.tools[${i}].inputSchema`,
|
|
117
|
+
message: "inputSchema must be a JSON Schema object"
|
|
118
|
+
});
|
|
119
|
+
valid = false;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
seenNames.add(obj.name);
|
|
123
|
+
const descriptor = { name: obj.name };
|
|
124
|
+
if (typeof obj.description === "string") {
|
|
125
|
+
descriptor.description = obj.description;
|
|
126
|
+
}
|
|
127
|
+
if (obj.inputSchema !== void 0) {
|
|
128
|
+
descriptor.inputSchema = obj.inputSchema;
|
|
129
|
+
}
|
|
130
|
+
tools.push(descriptor);
|
|
131
|
+
}
|
|
132
|
+
if (valid) entry.tools = tools;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (typeof v !== "string") {
|
|
136
|
+
issues.push({
|
|
137
|
+
path: `agents.${name}.${k}`,
|
|
138
|
+
message: "must be a string"
|
|
139
|
+
});
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
;
|
|
143
|
+
entry[k] = v;
|
|
144
|
+
}
|
|
145
|
+
if (entry.voiceId && !entry.firstMessage) {
|
|
146
|
+
issues.push({
|
|
147
|
+
path: `agents.${name}.firstMessage`,
|
|
148
|
+
message: "voiceId is set; consider providing firstMessage so the agent has a greeting"
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
agents[name] = entry;
|
|
152
|
+
}
|
|
153
|
+
return { agents, issues };
|
|
154
|
+
}
|
|
155
|
+
function normalizeToolEntry(entry) {
|
|
156
|
+
if (typeof entry === "string") return { name: entry };
|
|
157
|
+
const out = { name: entry.name };
|
|
158
|
+
if (entry.description !== void 0) out.description = entry.description;
|
|
159
|
+
if (entry.inputSchema !== void 0) out.inputSchema = entry.inputSchema;
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
function buildSyncPayload(opts) {
|
|
163
|
+
const out = { agents: {}, prune: opts.prune, dryRun: opts.dryRun };
|
|
164
|
+
for (const [name, entry] of Object.entries(opts.manifest)) {
|
|
165
|
+
const a = {};
|
|
166
|
+
if (entry.systemPrompt !== void 0) a.systemPrompt = entry.systemPrompt;
|
|
167
|
+
if (entry.tools !== void 0) {
|
|
168
|
+
a.tools = entry.tools.map(normalizeToolEntry);
|
|
169
|
+
}
|
|
170
|
+
if (entry.characterName !== void 0) a.characterName = entry.characterName;
|
|
171
|
+
if (entry.displayName !== void 0) a.displayName = entry.displayName;
|
|
172
|
+
if (entry.voiceId !== void 0) a.voiceId = entry.voiceId;
|
|
173
|
+
if (entry.firstMessage !== void 0) a.firstMessage = entry.firstMessage;
|
|
174
|
+
if (entry.model !== void 0) a.model = entry.model;
|
|
175
|
+
out.agents[name] = a;
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
function resolveDeployHeaders(options) {
|
|
180
|
+
const headers = {
|
|
181
|
+
"content-type": "application/json"
|
|
182
|
+
};
|
|
183
|
+
const auth = options.auth ?? (options.shogoApiKey ? { kind: "apiKey", apiKey: options.shogoApiKey } : void 0);
|
|
184
|
+
if (!auth) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
'runDeploy: missing auth \u2014 pass `auth: { kind: "apiKey", apiKey }` or `auth: { kind: "runtimeToken", token }`'
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
if (auth.kind === "apiKey") {
|
|
190
|
+
headers.authorization = `Bearer ${auth.apiKey}`;
|
|
191
|
+
} else {
|
|
192
|
+
headers["x-runtime-token"] = auth.token;
|
|
193
|
+
}
|
|
194
|
+
return headers;
|
|
195
|
+
}
|
|
196
|
+
async function runDeploy(options) {
|
|
197
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
198
|
+
if (typeof fetchImpl !== "function") {
|
|
199
|
+
throw new Error("runDeploy: global fetch is unavailable; pass options.fetchImpl");
|
|
200
|
+
}
|
|
201
|
+
const url = `${options.apiUrl.replace(/\/+$/, "")}/api/projects/${encodeURIComponent(options.projectId)}/agents/sync`;
|
|
202
|
+
const payload = buildSyncPayload({
|
|
203
|
+
manifest: options.manifest,
|
|
204
|
+
prune: options.prune,
|
|
205
|
+
dryRun: options.dryRun
|
|
206
|
+
});
|
|
207
|
+
const res = await fetchImpl(url, {
|
|
208
|
+
method: "POST",
|
|
209
|
+
headers: resolveDeployHeaders(options),
|
|
210
|
+
body: JSON.stringify(payload)
|
|
211
|
+
});
|
|
212
|
+
let body = null;
|
|
213
|
+
try {
|
|
214
|
+
body = await res.json();
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
return { status: res.status, body };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export {
|
|
221
|
+
AGENT_NAME_PATTERN,
|
|
222
|
+
validateManifest,
|
|
223
|
+
normalizeToolEntry,
|
|
224
|
+
buildSyncPayload,
|
|
225
|
+
runDeploy
|
|
226
|
+
};
|
|
227
|
+
//# sourceMappingURL=chunk-YYO73V5H.js.map
|