@someonegg/agent-hub 0.0.0-stage → 0.7.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 +104 -2
- package/README.zh-CN.md +99 -0
- package/README.zh-TW.md +99 -0
- package/dist/client/apple-touch-icon.png +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/client/assets/index-4ejyszaV.css +1 -0
- package/dist/client/assets/index-CKKT4cMK.js +309 -0
- package/dist/client/favicon.ico +0 -0
- package/dist/client/favicon.svg +6 -0
- package/dist/client/icons/icon-192.png +0 -0
- package/dist/client/icons/icon-512.png +0 -0
- package/dist/client/icons/icon-maskable-512.png +0 -0
- package/dist/client/index.html +22 -0
- package/dist/client/manifest.webmanifest +21 -0
- package/dist/server/server/adapters/claude/claude-session-source.js +220 -0
- package/dist/server/server/adapters/claude/identity.js +28 -0
- package/dist/server/server/adapters/claude/interaction.js +68 -0
- package/dist/server/server/adapters/claude/prompt-readiness.js +12 -0
- package/dist/server/server/adapters/claude/ready-script.js +25 -0
- package/dist/server/server/adapters/claude/session-manager.js +106 -0
- package/dist/server/server/adapters/claude/session-normalizer.js +173 -0
- package/dist/server/server/adapters/claude/user-input-normalizer.js +71 -0
- package/dist/server/server/adapters/codex/catalog-source.js +2 -0
- package/dist/server/server/adapters/codex/codex-session-source.js +250 -0
- package/dist/server/server/adapters/codex/identity-resolver.js +104 -0
- package/dist/server/server/adapters/codex/interaction-parser.js +70 -0
- package/dist/server/server/adapters/codex/io-errors.js +2 -0
- package/dist/server/server/adapters/codex/jsonl-catalog-source.js +84 -0
- package/dist/server/server/adapters/codex/limits.js +2 -0
- package/dist/server/server/adapters/codex/message-normalizer.js +66 -0
- package/dist/server/server/adapters/codex/response-item-parser.js +64 -0
- package/dist/server/server/adapters/codex/rollout-descriptor.js +2 -0
- package/dist/server/server/adapters/codex/session-index-reader.js +101 -0
- package/dist/server/server/adapters/codex/session-manager.js +51 -0
- package/dist/server/server/adapters/codex/session-normalizer.js +175 -0
- package/dist/server/server/adapters/codex/subagent-identity.js +9 -0
- package/dist/server/server/adapters/codex/user-input-normalizer.js +121 -0
- package/dist/server/server/adapters/codex/value-readers.js +4 -0
- package/dist/server/server/adapters/shared/binding-protocol.js +54 -0
- package/dist/server/server/adapters/shared/io-errors.js +13 -0
- package/dist/server/server/adapters/shared/jsonl-decoder.js +263 -0
- package/dist/server/server/adapters/shared/limits.js +4 -0
- package/dist/server/server/adapters/shared/session-management.js +43 -0
- package/dist/server/server/adapters/shared/source-cache.js +23 -0
- package/dist/server/server/api/session-api-mapper.js +65 -0
- package/dist/server/server/application/interaction-session-reader.js +17 -0
- package/dist/server/server/application/session-read-service.js +86 -0
- package/dist/server/server/application/session-reader.js +2 -0
- package/dist/server/server/application/session-response.js +11 -0
- package/dist/server/server/catalog/config.js +4 -0
- package/dist/server/server/catalog/session-catalog-service.js +112 -0
- package/dist/server/server/cli/command-error.js +27 -0
- package/dist/server/server/cli/main.js +42 -0
- package/dist/server/server/cli/output.js +52 -0
- package/dist/server/server/cli/resource.js +104 -0
- package/dist/server/server/cli/session-client.js +64 -0
- package/dist/server/server/cli/session-follow.js +58 -0
- package/dist/server/server/cli/session-output.js +184 -0
- package/dist/server/server/cli/session-wait.js +44 -0
- package/dist/server/server/cli/session.js +318 -0
- package/dist/server/server/cli/skill.js +37 -0
- package/dist/server/server/config.js +196 -0
- package/dist/server/server/create-hub.js +91 -0
- package/dist/server/server/directory-lock.js +65 -0
- package/dist/server/server/domain/project-directory.js +5 -0
- package/dist/server/server/domain/session-domain.js +2 -0
- package/dist/server/server/domain/session-text.js +45 -0
- package/dist/server/server/hosting/diff-store.js +103 -0
- package/dist/server/server/hosting/routes.js +168 -0
- package/dist/server/server/hosting/store.js +162 -0
- package/dist/server/server/hosting/validation-error.js +4 -0
- package/dist/server/server/http/api-route-handler.js +2 -0
- package/dist/server/server/http/api-router.js +54 -0
- package/dist/server/server/http/create-server.js +218 -0
- package/dist/server/server/http/query-params.js +19 -0
- package/dist/server/server/http/request-body.js +48 -0
- package/dist/server/server/http/request-errors.js +59 -0
- package/dist/server/server/http/router.js +16 -0
- package/dist/server/server/http/security.js +19 -0
- package/dist/server/server/http/session-interaction-routes.js +128 -0
- package/dist/server/server/http/session-management-routes.js +105 -0
- package/dist/server/server/http/session-path.js +10 -0
- package/dist/server/server/http/session-read-routes.js +143 -0
- package/dist/server/server/http/terminal-preview-websocket.js +179 -0
- package/dist/server/server/http/upload-routes.js +124 -0
- package/dist/server/server/interaction/interaction-service.js +276 -0
- package/dist/server/server/interaction/message.js +19 -0
- package/dist/server/server/interaction/pane-binding-manager.js +62 -0
- package/dist/server/server/interaction/pane-observation.js +31 -0
- package/dist/server/server/interaction/reconcile-session-state.js +21 -0
- package/dist/server/server/interaction/runtime-capabilities.js +2 -0
- package/dist/server/server/interaction/session-runtime.js +86 -0
- package/dist/server/server/interaction/terminal-activity-service.js +81 -0
- package/dist/server/server/interaction/terminal-preview-diff.js +71 -0
- package/dist/server/server/interaction/terminal-preview-service.js +184 -0
- package/dist/server/server/interaction/tmux-launcher.js +84 -0
- package/dist/server/server/interaction/tmux-preview-listener.js +107 -0
- package/dist/server/server/interaction/tmux-service.js +334 -0
- package/dist/server/server/interaction/tmux-target.js +13 -0
- package/dist/server/server/interaction/transport.js +9 -0
- package/dist/server/server/main.js +54 -0
- package/dist/server/server/management/operation-service.js +307 -0
- package/dist/server/server/management/operation-state.js +3 -0
- package/dist/server/server/management/session-exit-service.js +50 -0
- package/dist/server/server/management/session-launch-executor.js +164 -0
- package/dist/server/server/management/session-lifecycle-executor.js +158 -0
- package/dist/server/server/management/session-manager.js +64 -0
- package/dist/server/server/repository/catalog-snapshot-store.js +318 -0
- package/dist/server/server/repository/diagnostic-logger.js +70 -0
- package/dist/server/server/repository/refresh-coordinator.js +14 -0
- package/dist/server/server/repository/session-queries.js +72 -0
- package/dist/server/server/security/opaque-id.js +17 -0
- package/dist/server/server/source/session-source.js +2 -0
- package/dist/server/server/updates/session-updates-service.js +151 -0
- package/dist/server/server/updates/update-revision.js +22 -0
- package/dist/server/server/uploads/store.js +304 -0
- package/dist/server/server/version.js +21 -0
- package/dist/server/shared/api-contract.js +2 -0
- package/dist/server/shared/diff.js +158 -0
- package/dist/server/shared/domain.js +2 -0
- package/dist/server/shared/interaction.js +22 -0
- package/dist/server/shared/task-name.js +6 -0
- package/dist/server/shared/uploads.js +8 -0
- package/package.json +98 -4
- package/skills/ahub-control/SKILL.md +93 -0
- package/skills/ahub-publish/SKILL.md +54 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CommandError } from "./command-error.js";
|
|
2
|
+
export const pauseSessionPolling = (signal, milliseconds = 500) => new Promise((accept, reject) => {
|
|
3
|
+
if (signal.aborted) {
|
|
4
|
+
reject(new Error("Command interrupted or timed out"));
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
const stop = () => {
|
|
8
|
+
clearTimeout(wait);
|
|
9
|
+
signal.removeEventListener("abort", stop);
|
|
10
|
+
reject(new Error("Command interrupted or timed out"));
|
|
11
|
+
};
|
|
12
|
+
const wait = setTimeout(() => {
|
|
13
|
+
signal.removeEventListener("abort", stop);
|
|
14
|
+
accept();
|
|
15
|
+
}, milliseconds);
|
|
16
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
17
|
+
});
|
|
18
|
+
export async function waitForOperation(operation, api, signal, observe = () => { }) {
|
|
19
|
+
observe(operation);
|
|
20
|
+
while (operation.status === "pending") {
|
|
21
|
+
await pauseSessionPolling(signal);
|
|
22
|
+
operation = await api(`/api/v1/operations/${encodeURIComponent(operation.operationId)}`);
|
|
23
|
+
observe(operation);
|
|
24
|
+
}
|
|
25
|
+
if (operation.status === "unknown")
|
|
26
|
+
throw new CommandError("operation_result_unknown", operation.error ?? "Operation result unknown; check the current session state.", 2);
|
|
27
|
+
if (operation.status !== "succeeded")
|
|
28
|
+
throw new CommandError("operation_failed", operation.error ?? "Operation failed; check the server diagnostics.");
|
|
29
|
+
return operation;
|
|
30
|
+
}
|
|
31
|
+
export async function waitForLaunch(sessionId, api, signal) {
|
|
32
|
+
if (!sessionId)
|
|
33
|
+
throw new Error("Launch response has no session");
|
|
34
|
+
for (;;) {
|
|
35
|
+
const observed = await api(`/api/v1/sessions/${encodeURIComponent(sessionId)}/messages?limit=1`);
|
|
36
|
+
if (observed.session.status === "live" &&
|
|
37
|
+
observed.interaction.supported &&
|
|
38
|
+
observed.interaction.state === "connected" &&
|
|
39
|
+
observed.interaction.activity === "idle")
|
|
40
|
+
break;
|
|
41
|
+
await pauseSessionPolling(signal);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=session-wait.js.map
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { INTERACTION_KEYS, MAX_INTERACTION_KEY_SEQUENCE_LENGTH, MAX_INTERACTION_MESSAGE_BYTES, } from "../../shared/api-contract.js";
|
|
5
|
+
import { TASK_NAME_ERROR, validTaskName } from "../../shared/task-name.js";
|
|
6
|
+
import { normalizeMessage } from "../interaction/message.js";
|
|
7
|
+
import { CommandError, commandFailure } from "./command-error.js";
|
|
8
|
+
import { output, outputFailure } from "./output.js";
|
|
9
|
+
import { recoverMessageQuery, SessionClient } from "./session-client.js";
|
|
10
|
+
import { followInspect } from "./session-follow.js";
|
|
11
|
+
import { formatResult, mapInspect, mapList, mapTerminal, } from "./session-output.js";
|
|
12
|
+
import { waitForLaunch, waitForOperation } from "./session-wait.js";
|
|
13
|
+
export const sessionHelp = `Usage: ahub-cli session <command> [options]
|
|
14
|
+
list [--project <directory>] [--task <name>] [--all]
|
|
15
|
+
create --project <directory> [--task <name>] [--cwd <directory>] [--agent <nickname>] [--worktree] [--yolo]
|
|
16
|
+
resume <id> [--yolo]
|
|
17
|
+
send <id> (--message <text> | --stdin) [--force]
|
|
18
|
+
inspect <id> [--limit 1-300] [--after <number>] [--follow [--count <N>]]
|
|
19
|
+
terminal <id>
|
|
20
|
+
keys <id> <key...> [--force]
|
|
21
|
+
interrupt <id>
|
|
22
|
+
handoff <id>
|
|
23
|
+
exit <id> | archive <id> | delete <id>
|
|
24
|
+
Options: --url <http(s)-url> --json --timeout <seconds> (default 15; follow: 30; create/resume/exit/archive/delete: 45)
|
|
25
|
+
List: excludes archived sessions unless --all is set.
|
|
26
|
+
Follow: timeout applies to each HTTP request; observation continues until stopped.
|
|
27
|
+
Address: --url, AGENT_HUB_URL, http://127.0.0.1:4173
|
|
28
|
+
Messages: update indicates progress; response indicates a reply, not verified task completion.
|
|
29
|
+
Input: send/keys require connected and idle; --force skips the idle check, allowing input while working.
|
|
30
|
+
Send: messages starting with / are rejected by the CLI; --force skips this check too.
|
|
31
|
+
Keys: ${INTERACTION_KEYS.filter((key) => key !== "interrupt").join(", ")}
|
|
32
|
+
Completion: idle/response do not end --follow; verify results before exit/archive/delete.`;
|
|
33
|
+
export function parseSessionArguments(args, environmentUrl) {
|
|
34
|
+
const { values, positionals } = validateArguments(() => parseArgs({
|
|
35
|
+
args,
|
|
36
|
+
allowPositionals: true,
|
|
37
|
+
options: {
|
|
38
|
+
url: { type: "string" },
|
|
39
|
+
json: { type: "boolean" },
|
|
40
|
+
timeout: { type: "string" },
|
|
41
|
+
project: { type: "string" },
|
|
42
|
+
task: { type: "string" },
|
|
43
|
+
all: { type: "boolean" },
|
|
44
|
+
cwd: { type: "string" },
|
|
45
|
+
agent: { type: "string" },
|
|
46
|
+
worktree: { type: "boolean" },
|
|
47
|
+
yolo: { type: "boolean" },
|
|
48
|
+
message: { type: "string" },
|
|
49
|
+
stdin: { type: "boolean" },
|
|
50
|
+
force: { type: "boolean" },
|
|
51
|
+
limit: { type: "string" },
|
|
52
|
+
after: { type: "string" },
|
|
53
|
+
follow: { type: "boolean" },
|
|
54
|
+
count: { type: "string" },
|
|
55
|
+
help: { type: "boolean" },
|
|
56
|
+
},
|
|
57
|
+
}));
|
|
58
|
+
if (values.help || positionals.length === 0) {
|
|
59
|
+
return { values, positionals, help: true };
|
|
60
|
+
}
|
|
61
|
+
const [command, id, ...keys] = positionals;
|
|
62
|
+
const allowed = {
|
|
63
|
+
list: ["project", "task", "all"],
|
|
64
|
+
create: ["task", "cwd", "project", "agent", "worktree", "yolo"],
|
|
65
|
+
resume: ["yolo"],
|
|
66
|
+
send: ["message", "stdin", "force"],
|
|
67
|
+
inspect: ["limit", "after", "follow", "count"],
|
|
68
|
+
terminal: [],
|
|
69
|
+
keys: ["force"],
|
|
70
|
+
interrupt: [],
|
|
71
|
+
handoff: [],
|
|
72
|
+
exit: [],
|
|
73
|
+
archive: [],
|
|
74
|
+
delete: [],
|
|
75
|
+
};
|
|
76
|
+
if (!Object.hasOwn(allowed, command))
|
|
77
|
+
throw new CommandError("invalid_arguments", "Unknown session command");
|
|
78
|
+
for (const key of Object.keys(values)) {
|
|
79
|
+
if (!["url", "json", "timeout", "help", ...allowed[command]].includes(key))
|
|
80
|
+
throw new CommandError("invalid_arguments", `--${key} is not supported by ${command}`);
|
|
81
|
+
}
|
|
82
|
+
if (values.task !== undefined && !validTaskName(values.task))
|
|
83
|
+
throw new CommandError("invalid_arguments", TASK_NAME_ERROR);
|
|
84
|
+
const acceptsSessionId = command !== "list" && command !== "create";
|
|
85
|
+
const invalidPositionals = acceptsSessionId
|
|
86
|
+
? !id || (command !== "keys" && positionals.length !== 2)
|
|
87
|
+
: positionals.length !== 1;
|
|
88
|
+
if (invalidPositionals)
|
|
89
|
+
throw new CommandError("invalid_arguments", "Invalid session command arguments");
|
|
90
|
+
if (command === "create" && !values.project)
|
|
91
|
+
throw new CommandError("invalid_arguments", "create requires --project");
|
|
92
|
+
if (command === "send" &&
|
|
93
|
+
Number(values.message !== undefined) + Number(values.stdin === true) !== 1)
|
|
94
|
+
throw new CommandError("invalid_arguments", "send requires exactly one of --message and --stdin");
|
|
95
|
+
if (command === "keys" &&
|
|
96
|
+
(keys.length === 0 ||
|
|
97
|
+
keys.length > MAX_INTERACTION_KEY_SEQUENCE_LENGTH ||
|
|
98
|
+
keys.some((key) => key === "interrupt" || !INTERACTION_KEYS.includes(key))))
|
|
99
|
+
throw new CommandError("invalid_arguments", "Unsupported key sequence; use session interrupt for interruption");
|
|
100
|
+
const defaultSeconds = ["create", "resume", "exit", "archive", "delete"].includes(command)
|
|
101
|
+
? 45
|
|
102
|
+
: values.follow
|
|
103
|
+
? 30
|
|
104
|
+
: 15;
|
|
105
|
+
const seconds = values.timeout === undefined ? defaultSeconds : Number(values.timeout);
|
|
106
|
+
if (!Number.isFinite(seconds) || seconds <= 0 || seconds * 1000 > 2_147_483_647)
|
|
107
|
+
throw new CommandError("invalid_arguments", "timeout must be a positive number of seconds within the supported range");
|
|
108
|
+
const base = validateArguments(() => new URL(values.url ?? environmentUrl ?? "http://127.0.0.1:4173"));
|
|
109
|
+
if (base.protocol !== "http:" && base.protocol !== "https:")
|
|
110
|
+
throw new CommandError("invalid_arguments", "Only HTTP and HTTPS URLs are supported");
|
|
111
|
+
if (base.username || base.password || base.search || base.hash || base.pathname !== "/")
|
|
112
|
+
throw new CommandError("invalid_arguments", "URL must contain only an HTTP or HTTPS origin");
|
|
113
|
+
if (values.count !== undefined && !values.follow)
|
|
114
|
+
throw new CommandError("invalid_arguments", "--count requires --follow");
|
|
115
|
+
if (values.count !== undefined &&
|
|
116
|
+
(!/^\d+$/.test(values.count) ||
|
|
117
|
+
!Number.isSafeInteger(Number(values.count)) ||
|
|
118
|
+
Number(values.count) < 1))
|
|
119
|
+
throw new CommandError("invalid_arguments", "count must be a positive safe integer");
|
|
120
|
+
if (command === "inspect") {
|
|
121
|
+
for (const name of ["limit", "after"]) {
|
|
122
|
+
const value = values[name];
|
|
123
|
+
if (value === undefined)
|
|
124
|
+
continue;
|
|
125
|
+
if (!/^\d+$/.test(value) ||
|
|
126
|
+
!Number.isSafeInteger(Number(value)) ||
|
|
127
|
+
(name === "limit" && (Number(value) < 1 || Number(value) > 300)))
|
|
128
|
+
throw new CommandError("invalid_arguments", `Invalid ${name}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (command === "send" && values.message !== undefined)
|
|
132
|
+
validateSendMessage(values.message, values.force);
|
|
133
|
+
return { values, positionals, command, id, keys, seconds, base, help: false };
|
|
134
|
+
}
|
|
135
|
+
/** One invocation owns one deadline and never retries a submitted mutation. */
|
|
136
|
+
export async function runSessionCommand(args) {
|
|
137
|
+
let json = args.includes("--json");
|
|
138
|
+
let client;
|
|
139
|
+
let timer;
|
|
140
|
+
let following = false;
|
|
141
|
+
let launchDiagnostics;
|
|
142
|
+
const abort = new AbortController();
|
|
143
|
+
const interrupt = () => abort.abort();
|
|
144
|
+
process.once("SIGINT", interrupt);
|
|
145
|
+
process.once("SIGTERM", interrupt);
|
|
146
|
+
try {
|
|
147
|
+
const parsed = parseSessionArguments(args, process.env.AGENT_HUB_URL);
|
|
148
|
+
const { values } = parsed;
|
|
149
|
+
json = values.json ?? false;
|
|
150
|
+
if (parsed.help) {
|
|
151
|
+
console.log(sessionHelp);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const { command, id, keys, seconds, base } = parsed;
|
|
155
|
+
following = values.follow === true;
|
|
156
|
+
if (!following)
|
|
157
|
+
timer = setTimeout(() => abort.abort(), seconds * 1000);
|
|
158
|
+
client = new SessionClient(base, abort.signal);
|
|
159
|
+
const api = client.request;
|
|
160
|
+
const path = `/api/v1/sessions/${encodeURIComponent(id ?? "")}`;
|
|
161
|
+
let result;
|
|
162
|
+
if (command === "list") {
|
|
163
|
+
const list = await api("/api/v1/sessions");
|
|
164
|
+
const project = values.project === undefined ? undefined : resolve(values.project);
|
|
165
|
+
const task = values.task?.trim();
|
|
166
|
+
const taskList = task === undefined
|
|
167
|
+
? list
|
|
168
|
+
: { ...list, sessions: list.sessions.filter((session) => session.taskName === task) };
|
|
169
|
+
result = mapList(taskList, project, values.all);
|
|
170
|
+
}
|
|
171
|
+
else if (command === "inspect") {
|
|
172
|
+
const query = new URLSearchParams();
|
|
173
|
+
for (const name of ["limit", "after"]) {
|
|
174
|
+
if (values[name] !== undefined)
|
|
175
|
+
query.set(name, values[name]);
|
|
176
|
+
}
|
|
177
|
+
if (following) {
|
|
178
|
+
await followInspect({
|
|
179
|
+
path,
|
|
180
|
+
query,
|
|
181
|
+
base,
|
|
182
|
+
signal: abort.signal,
|
|
183
|
+
timeoutMs: seconds * 1000,
|
|
184
|
+
count: values.count === undefined ? undefined : Number(values.count),
|
|
185
|
+
json,
|
|
186
|
+
});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
result = mapInspect(await api(`${path}/messages?${query}`));
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
if (!recoverMessageQuery(error, query))
|
|
194
|
+
throw error;
|
|
195
|
+
result = mapInspect(await api(`${path}/messages?${query}`));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else if (command === "terminal")
|
|
199
|
+
result = mapTerminal(await api(`${path}/terminal`));
|
|
200
|
+
else if (["send", "keys", "interrupt", "handoff"].includes(command)) {
|
|
201
|
+
if (command === "send") {
|
|
202
|
+
const input = values.stdin ? await readStdin(abort.signal) : values.message;
|
|
203
|
+
const message = validateSendMessage(input, values.force);
|
|
204
|
+
await api(`${path}/messages`, "POST", { message, requireIdle: !values.force });
|
|
205
|
+
}
|
|
206
|
+
else if (command === "handoff")
|
|
207
|
+
await api(`${path}/handoff-pane-binding`, "POST", {});
|
|
208
|
+
else
|
|
209
|
+
await api(`${path}/keys`, "POST", command === "interrupt" ? { keys: ["interrupt"] } : { keys, requireIdle: !values.force });
|
|
210
|
+
result = { sessionId: id, status: "sent" };
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
const launch = command === "create" || command === "resume";
|
|
214
|
+
let operationPath = command === "delete" ? path : `${path}/${command}`;
|
|
215
|
+
let body = {};
|
|
216
|
+
if (command === "create") {
|
|
217
|
+
operationPath = "/api/v1/sessions";
|
|
218
|
+
body = {
|
|
219
|
+
projectDirectory: resolve(values.project),
|
|
220
|
+
taskName: values.task?.trim(),
|
|
221
|
+
...(values.cwd === undefined ? {} : { workingDirectory: resolve(values.cwd) }),
|
|
222
|
+
worktree: values.worktree ?? false,
|
|
223
|
+
yolo: values.yolo ?? false,
|
|
224
|
+
...(values.agent === undefined ? {} : { agent: values.agent }),
|
|
225
|
+
requestId: randomUUID(),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
else if (command === "resume") {
|
|
229
|
+
operationPath = "/api/v1/sessions";
|
|
230
|
+
body = { resumeSessionId: id, yolo: values.yolo ?? false, requestId: randomUUID() };
|
|
231
|
+
}
|
|
232
|
+
if (launch)
|
|
233
|
+
launchDiagnostics = { sessionId: command === "resume" ? id : null, tmux: null };
|
|
234
|
+
let operation = await api(operationPath, command === "delete" ? "DELETE" : "POST", body);
|
|
235
|
+
operation = await waitForOperation(operation, api, abort.signal, (current) => {
|
|
236
|
+
if (launchDiagnostics) {
|
|
237
|
+
launchDiagnostics.sessionId = current.sessionId ?? launchDiagnostics.sessionId;
|
|
238
|
+
if (current.target)
|
|
239
|
+
launchDiagnostics.tmux = {
|
|
240
|
+
socketPath: current.target.socketPath,
|
|
241
|
+
sessionName: current.target.sessionName,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
if (launch)
|
|
246
|
+
await waitForLaunch(operation.sessionId, api, abort.signal);
|
|
247
|
+
result = {
|
|
248
|
+
sessionId: operation.sessionId,
|
|
249
|
+
status: "succeeded",
|
|
250
|
+
...(launch ? { tmux: launchDiagnostics?.tmux ?? null } : {}),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
output(result, json, json ? "" : formatResult(command, result));
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
if (following && abort.signal.aborted)
|
|
257
|
+
return;
|
|
258
|
+
const failure = commandFailure(error, client?.mutationSubmitted ?? false);
|
|
259
|
+
if (launchDiagnostics)
|
|
260
|
+
failure.diagnostics = launchDiagnostics;
|
|
261
|
+
outputFailure(failure, json);
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
clearTimeout(timer);
|
|
265
|
+
process.removeListener("SIGINT", interrupt);
|
|
266
|
+
process.removeListener("SIGTERM", interrupt);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async function readStdin(signal) {
|
|
270
|
+
return new Promise((accept, reject) => {
|
|
271
|
+
const chunks = [];
|
|
272
|
+
let size = 0;
|
|
273
|
+
const cleanup = () => {
|
|
274
|
+
process.stdin.removeListener("data", data);
|
|
275
|
+
process.stdin.removeListener("end", end);
|
|
276
|
+
process.stdin.removeListener("error", fail);
|
|
277
|
+
signal.removeEventListener("abort", stop);
|
|
278
|
+
process.stdin.pause();
|
|
279
|
+
};
|
|
280
|
+
const fail = (error) => {
|
|
281
|
+
cleanup();
|
|
282
|
+
reject(error);
|
|
283
|
+
};
|
|
284
|
+
const stop = () => fail(new Error("Command interrupted or timed out"));
|
|
285
|
+
const data = (chunk) => {
|
|
286
|
+
size += chunk.length;
|
|
287
|
+
if (size > MAX_INTERACTION_MESSAGE_BYTES * 2)
|
|
288
|
+
fail(new CommandError("invalid_arguments", `Message exceeds ${MAX_INTERACTION_MESSAGE_BYTES / 1024} KiB`));
|
|
289
|
+
else
|
|
290
|
+
chunks.push(chunk);
|
|
291
|
+
};
|
|
292
|
+
const end = () => {
|
|
293
|
+
cleanup();
|
|
294
|
+
accept(Buffer.concat(chunks).toString("utf8"));
|
|
295
|
+
};
|
|
296
|
+
if (signal.aborted) {
|
|
297
|
+
stop();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
process.stdin.on("data", data).once("end", end).once("error", fail);
|
|
301
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function validateSendMessage(input, force = false) {
|
|
305
|
+
const message = validateArguments(() => normalizeMessage(input));
|
|
306
|
+
if (!force && message.startsWith("/"))
|
|
307
|
+
throw new CommandError("invalid_arguments", "Message must not start with /; use --force to send it intentionally");
|
|
308
|
+
return message;
|
|
309
|
+
}
|
|
310
|
+
function validateArguments(validate) {
|
|
311
|
+
try {
|
|
312
|
+
return validate();
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
throw new CommandError("invalid_arguments", error instanceof Error ? error.message : String(error));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { CommandError } from "./command-error.js";
|
|
5
|
+
const bundledSkills = ["ahub-publish", "ahub-control"];
|
|
6
|
+
const usage = `Usage: ahub-cli skill path <${bundledSkills.join(" | ")}>`;
|
|
7
|
+
export async function runSkillCommand(args) {
|
|
8
|
+
if (args.length === 0 || (args.length === 1 && args[0] === "--help")) {
|
|
9
|
+
console.log(usage);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const [command, skill] = args;
|
|
13
|
+
if (args.length !== 2 || command !== "path" || !skill || !bundledSkills.includes(skill))
|
|
14
|
+
throw new CommandError("invalid_arguments", usage);
|
|
15
|
+
let directory = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
while (true) {
|
|
17
|
+
let name;
|
|
18
|
+
try {
|
|
19
|
+
name = JSON.parse(await readFile(join(directory, "package.json"), "utf8")).name;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Continue through directories without a package manifest.
|
|
23
|
+
}
|
|
24
|
+
if (name === "@someonegg/agent-hub") {
|
|
25
|
+
const path = join(directory, "skills", skill);
|
|
26
|
+
if (!(await stat(join(path, "SKILL.md"))).isFile())
|
|
27
|
+
throw new CommandError("command_failed", "Skill file is missing");
|
|
28
|
+
console.log(path);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const parent = dirname(directory);
|
|
32
|
+
if (parent === directory)
|
|
33
|
+
throw new CommandError("command_failed", "Cannot locate the Hub package");
|
|
34
|
+
directory = parent;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=skill.js.map
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
export function resolveHomeDirectory(value) {
|
|
7
|
+
return value === undefined
|
|
8
|
+
? resolve(homedir(), ".agent-hub")
|
|
9
|
+
: resolvePathOption("home-dir", value);
|
|
10
|
+
}
|
|
11
|
+
export const LOOPBACK_HOST = "127.0.0.1";
|
|
12
|
+
function nonEmptyOption(name, value) {
|
|
13
|
+
if (value === undefined)
|
|
14
|
+
return undefined;
|
|
15
|
+
const normalized = value.trim();
|
|
16
|
+
if (normalized.length === 0) {
|
|
17
|
+
throw new Error(`--${name} must not be empty`);
|
|
18
|
+
}
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
21
|
+
function parsePort(value, option = "port") {
|
|
22
|
+
if (value === undefined)
|
|
23
|
+
return 4173;
|
|
24
|
+
const port = Number(value);
|
|
25
|
+
if (!Number.isInteger(port) || port < 0 || port > 65_535) {
|
|
26
|
+
throw new Error(`--${option} must be an integer between 0 and 65535`);
|
|
27
|
+
}
|
|
28
|
+
return port;
|
|
29
|
+
}
|
|
30
|
+
export function loadConfig(args = process.argv.slice(2)) {
|
|
31
|
+
const { values, tokens } = parseArgs({
|
|
32
|
+
tokens: true,
|
|
33
|
+
args: [...args],
|
|
34
|
+
allowPositionals: false,
|
|
35
|
+
strict: true,
|
|
36
|
+
options: {
|
|
37
|
+
"codex-agent": { type: "string", multiple: true },
|
|
38
|
+
"claude-agent": { type: "string", multiple: true },
|
|
39
|
+
host: { type: "string" },
|
|
40
|
+
port: { type: "string" },
|
|
41
|
+
"local-port": { type: "string" },
|
|
42
|
+
ssl: { type: "boolean" },
|
|
43
|
+
"ssl-cert": { type: "string" },
|
|
44
|
+
"ssl-key": { type: "string" },
|
|
45
|
+
"ssl-ca": { type: "string" },
|
|
46
|
+
help: { type: "boolean" },
|
|
47
|
+
version: { type: "boolean" },
|
|
48
|
+
"tmux-socket": { type: "string" },
|
|
49
|
+
"claude-ready-script": { type: "string" },
|
|
50
|
+
"home-dir": { type: "string" },
|
|
51
|
+
hosting: { type: "boolean" },
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
if (values.help)
|
|
55
|
+
return { kind: "help" };
|
|
56
|
+
if (values.version)
|
|
57
|
+
return { kind: "version" };
|
|
58
|
+
const host = nonEmptyOption("host", values.host) ?? LOOPBACK_HOST;
|
|
59
|
+
const names = new Set();
|
|
60
|
+
const paths = new Set();
|
|
61
|
+
let entries = tokens.flatMap((token) => token.kind === "option" && (token.name === "codex-agent" || token.name === "claude-agent")
|
|
62
|
+
? [{ option: token.name, value: token.value }]
|
|
63
|
+
: []);
|
|
64
|
+
if (entries.length === 0) {
|
|
65
|
+
entries = [
|
|
66
|
+
{ option: "codex-agent", value: "codex=~/.codex" },
|
|
67
|
+
{ option: "claude-agent", value: "claude=~/.claude" },
|
|
68
|
+
];
|
|
69
|
+
try {
|
|
70
|
+
statSync(resolve(homedir(), ".codex"));
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
const code = error.code;
|
|
74
|
+
if (code === "ENOENT")
|
|
75
|
+
entries.reverse();
|
|
76
|
+
else if (code !== "EACCES" && code !== "EPERM")
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const agents = entries.map(({ option, value }) => ({
|
|
81
|
+
...parseAgent(value, option, names, paths),
|
|
82
|
+
...(option === "claude-agent" ? { adapter: "claude" } : {}),
|
|
83
|
+
}));
|
|
84
|
+
const certificatePath = nonEmptyOption("ssl-cert", values["ssl-cert"]);
|
|
85
|
+
const privateKeyPath = nonEmptyOption("ssl-key", values["ssl-key"]);
|
|
86
|
+
const certificateAuthorityPath = nonEmptyOption("ssl-ca", values["ssl-ca"]);
|
|
87
|
+
let tls;
|
|
88
|
+
if (values.ssl) {
|
|
89
|
+
if (!certificatePath || !privateKeyPath) {
|
|
90
|
+
throw new Error("--ssl requires both --ssl-cert and --ssl-key");
|
|
91
|
+
}
|
|
92
|
+
tls = {
|
|
93
|
+
enabled: true,
|
|
94
|
+
certificatePath: resolvePathOption("ssl-cert", certificatePath),
|
|
95
|
+
privateKeyPath: resolvePathOption("ssl-key", privateKeyPath),
|
|
96
|
+
...(certificateAuthorityPath
|
|
97
|
+
? { certificateAuthorityPath: resolvePathOption("ssl-ca", certificateAuthorityPath) }
|
|
98
|
+
: {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
if (certificatePath || privateKeyPath || certificateAuthorityPath) {
|
|
103
|
+
throw new Error("--ssl-cert, --ssl-key, and --ssl-ca require --ssl");
|
|
104
|
+
}
|
|
105
|
+
tls = { enabled: false };
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
kind: "run",
|
|
109
|
+
config: {
|
|
110
|
+
host,
|
|
111
|
+
...(values["claude-ready-script"] !== undefined
|
|
112
|
+
? {
|
|
113
|
+
claudeReadyScript: resolvePathOption("claude-ready-script", values["claude-ready-script"]),
|
|
114
|
+
}
|
|
115
|
+
: {}),
|
|
116
|
+
port: parsePort(values.port),
|
|
117
|
+
...(values["local-port"] !== undefined
|
|
118
|
+
? { localPort: parsePort(values["local-port"], "local-port") }
|
|
119
|
+
: {}),
|
|
120
|
+
agents,
|
|
121
|
+
clientDirectory: fileURLToPath(new URL("../../client/", import.meta.url)),
|
|
122
|
+
tls,
|
|
123
|
+
interactionEnabled: true,
|
|
124
|
+
uploadDirectory: resolve(resolveHomeDirectory(values["home-dir"]), "uploads"),
|
|
125
|
+
...(values.hosting
|
|
126
|
+
? { hostingDirectory: resolve(resolveHomeDirectory(values["home-dir"]), "hosting") }
|
|
127
|
+
: {}),
|
|
128
|
+
...(values["tmux-socket"] !== undefined
|
|
129
|
+
? { tmuxSocket: resolvePathOption("tmux-socket", values["tmux-socket"]) }
|
|
130
|
+
: {}),
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export function commandLineHelp() {
|
|
135
|
+
return `Usage: agent-hub [options]
|
|
136
|
+
|
|
137
|
+
Options:
|
|
138
|
+
--hosting Enable trusted same-origin static web hosting
|
|
139
|
+
--home-dir <path> Hub data root (default: ~/.agent-hub; uploads/ and hosting/)
|
|
140
|
+
--codex-agent <nickname=path>
|
|
141
|
+
Codex agent; repeat for multiple agents
|
|
142
|
+
--claude-agent <nickname=path>
|
|
143
|
+
Claude Code agent; repeat for multiple agents
|
|
144
|
+
Default agents: codex=~/.codex, claude=~/.claude (Claude first if ~/.codex is absent).
|
|
145
|
+
Any explicit agent replaces both defaults; argument order determines the default agent.
|
|
146
|
+
--host <host> Listen host (default: 127.0.0.1)
|
|
147
|
+
--port <port> Listen port, or 0 for a free port (default: 4173)
|
|
148
|
+
--local-port <port> Additional HTTP listener on 127.0.0.1 (disabled by default)
|
|
149
|
+
--ssl Enable TLS; the port will accept HTTPS only
|
|
150
|
+
--ssl-cert <path> PEM server certificate or certificate chain
|
|
151
|
+
--ssl-key <path> PEM server private key
|
|
152
|
+
--ssl-ca <path> PEM CA bundle; enables mandatory client certificates
|
|
153
|
+
--claude-ready-script <path>
|
|
154
|
+
Executable Claude readiness checker (all agents)
|
|
155
|
+
--tmux-socket <path> tmux socket (default: tmux default selection)
|
|
156
|
+
--help Show this help
|
|
157
|
+
--version Show the current version
|
|
158
|
+
|
|
159
|
+
TLS example:
|
|
160
|
+
agent-hub --ssl --ssl-cert server.crt --ssl-key server.key
|
|
161
|
+
|
|
162
|
+
mTLS example:
|
|
163
|
+
agent-hub --ssl --ssl-cert server.crt --ssl-key server.key --ssl-ca ca.crt`;
|
|
164
|
+
}
|
|
165
|
+
function parseAgent(value, option, names, paths) {
|
|
166
|
+
const separator = value.indexOf("=");
|
|
167
|
+
const nickname = value.slice(0, separator).trim();
|
|
168
|
+
const agentPath = value.slice(separator + 1).trim();
|
|
169
|
+
if (separator < 0 || !nickname || !agentPath) {
|
|
170
|
+
throw new Error(`--${option} requires nickname=path with a non-empty nickname and path`);
|
|
171
|
+
}
|
|
172
|
+
const path = resolvePathOption(option, agentPath);
|
|
173
|
+
let canonical = path;
|
|
174
|
+
try {
|
|
175
|
+
canonical = realpathSync(path);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (!(error &&
|
|
179
|
+
typeof error === "object" &&
|
|
180
|
+
"code" in error &&
|
|
181
|
+
["ENOENT", "EACCES", "EPERM"].includes(String(error.code))))
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
if (names.has(nickname))
|
|
185
|
+
throw new Error(`Duplicate agent nickname: ${nickname}`);
|
|
186
|
+
if (paths.has(canonical))
|
|
187
|
+
throw new Error("Duplicate agent path");
|
|
188
|
+
names.add(nickname);
|
|
189
|
+
paths.add(canonical);
|
|
190
|
+
return { nickname, path };
|
|
191
|
+
}
|
|
192
|
+
function resolvePathOption(name, value) {
|
|
193
|
+
const path = nonEmptyOption(name, value);
|
|
194
|
+
return resolve(path.startsWith("~/") ? resolve(homedir(), path.slice(2)) : path);
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=config.js.map
|