@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 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