@dev-mainsequence/codex-plugin 0.0.0-stage → 0.4.2

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.
Files changed (57) hide show
  1. package/.codex-plugin/plugin.json +22 -0
  2. package/.mcp.json +16 -0
  3. package/LICENSE +21 -0
  4. package/NOTICE +13 -0
  5. package/README.md +155 -2
  6. package/assets/mainsequence-icon.png +0 -0
  7. package/assets/mainsequence-icon.svg +454 -0
  8. package/build-info.json +10 -0
  9. package/dist/hosts/codex.js +62 -0
  10. package/dist/hosts/codexPresentation.js +165 -0
  11. package/dist/hosts/discovery.js +138 -0
  12. package/dist/hosts/panel/panel.html +165 -0
  13. package/dist/package.json +1 -0
  14. package/dist/panel-server.js +135 -0
  15. package/dist/runner.js +71 -0
  16. package/dist/vendor/devtools/LICENSE +21 -0
  17. package/dist/vendor/devtools/NOTICE +12 -0
  18. package/dist/vendor/devtools/index.js +72 -0
  19. package/dist/vendor/devtools/package.json +5 -0
  20. package/dist/vendor/devtools/shared/accountScope.js +19 -0
  21. package/dist/vendor/devtools/shared/actions.js +164 -0
  22. package/dist/vendor/devtools/shared/agentReadiness.js +71 -0
  23. package/dist/vendor/devtools/shared/agents.js +240 -0
  24. package/dist/vendor/devtools/shared/arguments.js +142 -0
  25. package/dist/vendor/devtools/shared/cli.js +144 -0
  26. package/dist/vendor/devtools/shared/config.js +64 -0
  27. package/dist/vendor/devtools/shared/devcontainerJsonc.js +153 -0
  28. package/dist/vendor/devtools/shared/envfile.js +128 -0
  29. package/dist/vendor/devtools/shared/environments.js +38 -0
  30. package/dist/vendor/devtools/shared/hostSession.js +324 -0
  31. package/dist/vendor/devtools/shared/machineSession.js +338 -0
  32. package/dist/vendor/devtools/shared/mcp.js +421 -0
  33. package/dist/vendor/devtools/shared/mcpTransport.js +177 -0
  34. package/dist/vendor/devtools/shared/models.js +12 -0
  35. package/dist/vendor/devtools/shared/organizationProjectWorkspace.js +300 -0
  36. package/dist/vendor/devtools/shared/organizationProjects.js +134 -0
  37. package/dist/vendor/devtools/shared/process.js +61 -0
  38. package/dist/vendor/devtools/shared/projectOperations.js +279 -0
  39. package/dist/vendor/devtools/shared/projects.js +390 -0
  40. package/dist/vendor/devtools/shared/pythonProjectStatus.js +86 -0
  41. package/dist/vendor/devtools/shared/pythonSdkStatus.js +26 -0
  42. package/dist/vendor/devtools/shared/repositories.js +215 -0
  43. package/dist/vendor/devtools/shared/repositoryClient.js +108 -0
  44. package/dist/vendor/devtools/shared/repositoryIdentity.js +116 -0
  45. package/dist/vendor/devtools/shared/repositoryModels.js +72 -0
  46. package/dist/vendor/devtools/shared/session.js +532 -0
  47. package/docs/adr/0002-conversational-project-mcp-client.md +59 -0
  48. package/docs/agents-tools-panel.md +63 -0
  49. package/docs/codex-verification.md +161 -0
  50. package/docs/conversational-mcp.md +54 -0
  51. package/hooks/hooks.json +17 -0
  52. package/package.json +50 -4
  53. package/skills/mainsequence-mcp/SKILL.md +40 -0
  54. package/skills/ms/SKILL.md +8 -0
  55. package/skills/ms/agents/openai.yaml +7 -0
  56. package/skills/ms/assets/mainsequence-icon.png +0 -0
  57. package/skills/ms/assets/mainsequence-icon.svg +454 -0
@@ -0,0 +1,240 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentClient = void 0;
4
+ exports.isAgentAction = isAgentAction;
5
+ const node_crypto_1 = require("node:crypto");
6
+ const models_1 = require("./models.js");
7
+ const session_1 = require("./session.js");
8
+ const mcpTransport_1 = require("./mcpTransport.js");
9
+ const agentReadiness_1 = require("./agentReadiness.js");
10
+ function isAgentAction(action) {
11
+ return ['agents-list', 'agent-get', 'agent-session', 'agent-readiness', 'agent-send', 'agent-task-get', 'agent-task-wait', 'agent-task-cancel'].includes(action);
12
+ }
13
+ function uuid(value) {
14
+ if (typeof value !== 'string' || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(value)) {
15
+ throw new models_1.ToolingError('INVALID_REQUEST', 'Choose an agent and organization environment from the directory.');
16
+ }
17
+ return value.toLowerCase();
18
+ }
19
+ function text(value, limit = 16000) { return typeof value === 'string' ? value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/g, '').slice(0, limit) : ''; }
20
+ function agent(row, environmentUid) {
21
+ if (!row || uuid(row.organization_environment_uid) !== environmentUid)
22
+ throw new models_1.ToolingError('INVALID_REQUEST', 'This agent belongs to a different environment. Refresh the directory.');
23
+ if (!text(row.name).trim())
24
+ throw new models_1.ToolingError('API_ERROR', 'The agent directory returned an unnamed agent.');
25
+ return { uid: uuid(row.uid), name: text(row.name, 300), description: text(row.description), environmentUid,
26
+ environmentName: text(row.organization_environment_name, 300), repositoryBranch: text(row.repository_branch, 300),
27
+ runtimeReleaseUid: row.runtime_release_uid ? uuid(row.runtime_release_uid) : null,
28
+ runtimeState: text(row.runtime_update?.state, 50) || 'unknown',
29
+ defaults: { provider: text(row.llm_provider, 64).trim(), model: text(row.llm_model, 128).trim(), thinking: text(row.llm_thinking, 1000).trim() },
30
+ responseKinds: Array.isArray(row.a2a_profile?.supported_response_kinds) ? row.a2a_profile.supported_response_kinds.filter((value) => value === 'message' || value === 'task') : [],
31
+ skills: (Array.isArray(row.agent_card?.skills) ? row.agent_card.skills : []).slice(0, 50).map((skill) => ({ name: text(skill.name, 300), description: text(skill.description), examples: (Array.isArray(skill.examples) ? skill.examples : []).slice(0, 10).map((value) => text(value, 2000)) })) };
32
+ }
33
+ /** Host-independent platform Agent client. Never connects to a card's URL or exposes runtime credentials. */
34
+ class AgentClient {
35
+ session;
36
+ options;
37
+ constructor(session, options = {}) {
38
+ this.session = session;
39
+ this.options = options;
40
+ }
41
+ async dispatch(input, selectedBackend) {
42
+ let sent = false;
43
+ let readinessDeadline;
44
+ try {
45
+ const backend = (0, session_1.requireBackend)(selectedBackend), environmentUid = uuid(input.environmentUid);
46
+ this.options.signal?.throwIfAborted();
47
+ if (!isAgentAction(input.action))
48
+ throw new models_1.ToolingError('INVALID_REQUEST', 'Unknown agent action.');
49
+ const identity = await this.session.status(backend, true);
50
+ if (!identity.authenticated || !identity.username)
51
+ throw new models_1.ToolingError('NOT_AUTHENTICATED', 'Sign in to MainSequence to use agents.');
52
+ if (input.expectedAccount !== undefined && input.expectedAccount !== identity.username)
53
+ throw new models_1.ToolingError('AUTH_REJECTED', 'The MainSequence account changed. Refresh agents and start a new conversation.');
54
+ if (['agent-session', 'agent-readiness', 'agent-send', 'agent-task-get', 'agent-task-wait', 'agent-task-cancel'].includes(input.action) && !input.expectedAccount) {
55
+ throw new models_1.ToolingError('INVALID_REQUEST', 'Agent conversations must be bound to the account returned by discovery.');
56
+ }
57
+ // The built-in session binds every request/retry to the selected account. Legacy
58
+ // custom agent transports retain their existing account-check contract.
59
+ const session = input.expectedAccount !== undefined && this.session.withAccount
60
+ ? await this.session.withAccount(backend, input.expectedAccount) : this.session;
61
+ const timeouts = new Map();
62
+ const client = new mcpTransport_1.McpTransport(message => {
63
+ const remaining = readinessDeadline === undefined ? undefined : Math.ceil(readinessDeadline - performance.now());
64
+ if (remaining !== undefined && remaining <= 0)
65
+ throw new models_1.ToolingError('PROCESS_TIMEOUT', 'The agent did not become ready in time. No message was sent. Try again.');
66
+ return session.request(backend, 'POST', '/mcp', message, message.method !== 'tools/call' || !['a2a.send_message', 'a2a.cancel_task'].includes(message.params?.name), remaining !== undefined ? { timeoutMs: Math.min(30000, remaining) }
67
+ : message.method === 'tools/call' && timeouts.has(message.params?.name) ? { timeoutMs: timeouts.get(message.params.name) } : undefined);
68
+ });
69
+ await client.initialize();
70
+ const tools = await client.tools();
71
+ const catalog = new Set(tools.map(tool => tool.name));
72
+ for (const tool of tools) {
73
+ const seconds = tool._meta?.['mainsequence.ai/timeout-seconds/v1'];
74
+ if (typeof seconds === 'number' && Number.isFinite(seconds) && seconds > 0 && seconds <= 295) {
75
+ timeouts.set(tool.name, Math.ceil(seconds * 1000) + 5000);
76
+ }
77
+ }
78
+ // Older catalogs omit deadlines. Cover the existing 140-second gateway budget.
79
+ if (!timeouts.has('a2a.send_message'))
80
+ timeouts.set('a2a.send_message', 145000);
81
+ const call = async (name, args, mutation = false) => {
82
+ if (!catalog.has(name))
83
+ throw new models_1.ToolingError('UNSUPPORTED_ACTION', `The connected platform does not offer ${name}.`);
84
+ this.options.signal?.throwIfAborted();
85
+ if (mutation)
86
+ sent = true;
87
+ const output = await client.request('tools/call', { name, arguments: args });
88
+ if (output?.isError) {
89
+ const code = text(output.structuredContent?.body?.code, 100).replace(/[^a-zA-Z0-9_-]/g, '');
90
+ throw new models_1.ToolingError('API_ERROR', `${name} could not complete${code ? ` (${code})` : ''}. Check agent availability and access. The call was not replayed.`);
91
+ }
92
+ if (!output?.structuredContent || typeof output.structuredContent !== 'object')
93
+ throw new models_1.ToolingError('API_ERROR', 'The platform returned an invalid agent result.');
94
+ return output.structuredContent;
95
+ };
96
+ let data;
97
+ if (input.action === 'agents-list') {
98
+ const limit = input.limit ?? 25, offset = input.offset ?? 0;
99
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100 || !Number.isInteger(offset) || offset < 0
100
+ || (input.search !== undefined && (typeof input.search !== 'string' || input.search.length > 2000)))
101
+ throw new models_1.ToolingError('INVALID_REQUEST', 'Search requires text, a limit of 1–100, and a nonnegative offset.');
102
+ const search = input.search?.trim();
103
+ const page = search ? await call('agent.search', { organization_environment_uid: environmentUid, q: search, limit })
104
+ : await call('agent.list', { organization_environment_uid: environmentUid, limit, offset });
105
+ if (!Array.isArray(page.results) || (!search && (!Number.isInteger(page.count) || page.count < 0)))
106
+ throw new models_1.ToolingError('API_ERROR', 'The platform returned invalid agent pagination.');
107
+ data = { account: identity.username, results: page.results.map((row) => agent(row, environmentUid)), count: search ? page.results.length : page.count, offset: search ? 0 : offset, semantic: !!search };
108
+ }
109
+ else {
110
+ const agentUid = uuid(input.agentUid);
111
+ const selected = agent(await call('agent.get', { agent_uid: agentUid }), environmentUid);
112
+ if (selected.uid !== agentUid)
113
+ throw new models_1.ToolingError('API_ERROR', 'The platform returned another agent.');
114
+ const task = async (taskUid) => {
115
+ const result = await call('a2a.get_task', { task_uid: uuid(taskUid) });
116
+ if (uuid(result.agent_uid) !== agentUid || uuid(result.task_uid) !== uuid(taskUid))
117
+ throw new models_1.ToolingError('INVALID_REQUEST', 'This task belongs to another agent.');
118
+ return result;
119
+ };
120
+ const resolveSession = async (target) => {
121
+ const resolved = await call('agent.get_or_create_session', { agent_uid: agentUid, ...target });
122
+ if (uuid(resolved.agent_uid) !== agentUid || uuid(resolved.organization_environment_uid) !== environmentUid
123
+ || (target.session_uid && uuid(resolved.uid) !== uuid(target.session_uid))
124
+ || (target.handle_unique_id && resolved.bound_handle?.handle_unique_id !== target.handle_unique_id)) {
125
+ throw new models_1.ToolingError('API_ERROR', 'The platform returned another agent conversation.');
126
+ }
127
+ return resolved;
128
+ };
129
+ const ready = async (sessionUid, timeoutMs) => {
130
+ if (!selected.runtimeReleaseUid || selected.runtimeState === 'not_deployed')
131
+ throw new models_1.ToolingError('UNSUPPORTED_ACTION', 'This agent has no deployed runtime.');
132
+ try {
133
+ await (0, agentReadiness_1.waitForAgentReadiness)({ signal: this.options.signal, progress: this.options.progress, timeoutMs,
134
+ resolve: async (deadline) => {
135
+ readinessDeadline = deadline;
136
+ const account = await this.session.status(backend, true);
137
+ if (!account.authenticated || account.username !== identity.username)
138
+ throw new models_1.ToolingError('AUTH_REJECTED', 'The MainSequence account changed while checking the agent. Choose the agent again.');
139
+ return call('agent_session.resolve_runtime_access', { agent_session_uid: sessionUid });
140
+ } });
141
+ }
142
+ finally {
143
+ readinessDeadline = undefined;
144
+ }
145
+ };
146
+ if (input.action === 'agent-get')
147
+ data = { account: identity.username, agent: selected };
148
+ else if (input.action === 'agent-readiness') {
149
+ const resolved = await resolveSession({ session_uid: uuid(input.sessionUid) });
150
+ await ready(uuid(resolved.uid), input.timeoutMs);
151
+ data = { account: identity.username, agentUid, sessionUid: uuid(resolved.uid), ready: true };
152
+ }
153
+ else if (input.action === 'agent-session') {
154
+ if ([input.handleId, input.sessionUid].filter(value => value !== undefined).length !== 1) {
155
+ throw new models_1.ToolingError('INVALID_REQUEST', 'Use exactly one conversation handle or session.');
156
+ }
157
+ let target;
158
+ if (input.sessionUid !== undefined)
159
+ target = { session_uid: uuid(input.sessionUid) };
160
+ else {
161
+ if (typeof input.handleId !== 'string' || !/^[-a-zA-Z0-9_]{1,255}$/.test(input.handleId)) {
162
+ throw new models_1.ToolingError('INVALID_REQUEST', 'A stable conversation handle is required.');
163
+ }
164
+ target = { handle_unique_id: input.handleId };
165
+ }
166
+ const resolved = await resolveSession(target);
167
+ data = { account: identity.username, agent: selected, sessionUid: uuid(resolved.uid),
168
+ ...(resolved.bound_handle?.handle_unique_id ? { handleId: text(resolved.bound_handle.handle_unique_id, 255) } : {}) };
169
+ }
170
+ else if (input.action === 'agent-send') {
171
+ if (typeof input.message !== 'string' || !input.message.trim() || input.message.length > 64000)
172
+ throw new models_1.ToolingError('INVALID_REQUEST', 'Enter a message of 1–64000 characters.');
173
+ if (!['message', 'task'].includes(input.responseKind) || !selected.responseKinds.includes(input.responseKind))
174
+ throw new models_1.ToolingError('UNSUPPORTED_ACTION', 'The agent does not advertise the requested response mode.');
175
+ if (!selected.runtimeReleaseUid || selected.runtimeState === 'not_deployed')
176
+ throw new models_1.ToolingError('UNSUPPORTED_ACTION', 'This agent has no deployed runtime.');
177
+ if ([input.handleId, input.sessionUid, input.taskUid].filter(value => value !== undefined).length !== 1)
178
+ throw new models_1.ToolingError('INVALID_REQUEST', 'Use exactly one conversation handle, session, or task.');
179
+ let target;
180
+ if (input.taskUid) {
181
+ if (input.responseKind !== 'task')
182
+ throw new models_1.ToolingError('INVALID_REQUEST', 'Continue an existing task in task mode.');
183
+ await task(input.taskUid);
184
+ target = { task_uid: uuid(input.taskUid) };
185
+ }
186
+ else if (input.sessionUid)
187
+ target = { agent_uid: agentUid, agent_session_uid: uuid(input.sessionUid) };
188
+ else {
189
+ if (typeof input.handleId !== 'string' || !/^[-a-zA-Z0-9_]{1,255}$/.test(input.handleId))
190
+ throw new models_1.ToolingError('INVALID_REQUEST', 'A stable conversation handle is required.');
191
+ target = { agent_uid: agentUid, handle_unique_id: input.handleId };
192
+ }
193
+ if (input.waitForReady !== undefined && typeof input.waitForReady !== 'boolean')
194
+ throw new models_1.ToolingError('INVALID_REQUEST', 'waitForReady must be a boolean.');
195
+ if (input.waitForReady) {
196
+ const resolved = await resolveSession(input.taskUid
197
+ ? { session_uid: uuid((await task(input.taskUid)).agent_session_uid) }
198
+ : input.sessionUid ? { session_uid: uuid(input.sessionUid) } : { handle_unique_id: input.handleId });
199
+ await ready(uuid(resolved.uid));
200
+ }
201
+ const reply = await call('a2a.send_message', { ...target, message: input.message, message_id: (0, node_crypto_1.randomUUID)(), response_kind: input.responseKind,
202
+ ...(input.responseKind === 'task' ? { completion_policy: 'poll' } : {}) }, true);
203
+ if (uuid(reply.agent_uid) !== agentUid || reply.response_kind !== input.responseKind || (input.sessionUid && uuid(reply.agent_session_uid) !== uuid(input.sessionUid)))
204
+ throw new models_1.ToolingError('API_ERROR', 'The agent response does not match this conversation.');
205
+ data = { account: identity.username, agent: selected, ...projectReply(reply) };
206
+ }
207
+ else {
208
+ const previous = await task(input.taskUid);
209
+ if (input.afterSequence !== undefined && (!Number.isInteger(input.afterSequence) || input.afterSequence < 0))
210
+ throw new models_1.ToolingError('INVALID_REQUEST', 'Task event cursor must be nonnegative.');
211
+ const result = input.action === 'agent-task-get' ? previous : await call(input.action === 'agent-task-wait' ? 'a2a.wait_task' : 'a2a.cancel_task', { task_uid: uuid(input.taskUid), ...(input.action === 'agent-task-wait' ? { after_sequence: input.afterSequence ?? previous.event_cursor, timeout_seconds: 15 } : {}) }, input.action === 'agent-task-cancel');
212
+ if (uuid(result.agent_uid) !== agentUid || uuid(result.task_uid) !== uuid(input.taskUid))
213
+ throw new models_1.ToolingError('API_ERROR', 'The task result does not match the selected agent.');
214
+ data = { account: identity.username, agent: selected, ...projectReply(result) };
215
+ }
216
+ }
217
+ return { schemaVersion: 1, action: input.action, status: 'success', summary: input.action === 'agent-send' ? 'Agent response received.' : 'Agent operation completed.', data };
218
+ }
219
+ catch (error) {
220
+ const failure = this.options.signal?.aborted ? new models_1.ToolingError('CANCELLED', 'Agent operation cancelled.')
221
+ : error instanceof models_1.ToolingError ? error : new models_1.ToolingError('API_ERROR', 'The agent operation could not complete.');
222
+ return { schemaVersion: 1, action: input.action, status: failure.code === 'CANCELLED' ? 'cancelled' : 'failure', summary: failure.message,
223
+ error: { code: failure.code, message: failure.message }, ...(sent ? { effects: { state: 'partial-or-uncertain', message: 'The remote agent may have received this request. Do not automatically resend it; inspect its session or task first.' } } : {}) };
224
+ }
225
+ }
226
+ }
227
+ exports.AgentClient = AgentClient;
228
+ function projectReply(reply) {
229
+ const result = { sessionUid: uuid(reply.agent_session_uid), responseKind: reply.response_kind || 'task' };
230
+ if (reply.response_kind === 'message') {
231
+ if (typeof reply.message !== 'string' || !reply.message.trim())
232
+ throw new models_1.ToolingError('API_ERROR', 'The agent returned an empty message.');
233
+ result.message = text(reply.message, 64000);
234
+ }
235
+ else
236
+ Object.assign(result, { taskUid: uuid(reply.task_uid), status: text(reply.status, 100), terminal: reply.terminal === true,
237
+ eventCursor: reply.event_cursor, cancellationRequested: reply.cancellation_requested === true,
238
+ outputs: Array.isArray(reply.outputs) ? reply.outputs : [], events: Array.isArray(reply.events) ? reply.events : [], timedOut: reply.timed_out === true });
239
+ return result;
240
+ }
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HELP = void 0;
4
+ exports.parseArguments = parseArguments;
5
+ exports.tokenizeCommand = tokenizeCommand;
6
+ const node_fs_1 = require("node:fs");
7
+ const models_1 = require("./models.js");
8
+ exports.HELP = `MainSequence developer tooling (Node 22+)
9
+
10
+ ms-devtools login|logout|refresh-token|auth-status|repositories [--backend URL]
11
+ ms-devtools auth-status [--check] [--backend URL]
12
+ ms-devtools mcp-discover [--search TEXT] [--limit N] [--offset N] [--backend URL]
13
+ ms-devtools mcp-list [--backend URL]
14
+ ms-devtools mcp-add|mcp-remove|mcp-tools --uid RELEASE_UUID [--backend URL]
15
+ ms-devtools mcp-call --uid RELEASE_UUID --tool NAME [--arguments-file JSON_FILE] [--backend URL]
16
+ ms-devtools status|resolve [--path ROOT] [--type python|vite_react] [--backend URL]
17
+ ms-devtools sync [--path ROOT]
18
+ ms-devtools sdk-status|update-sdk|update-agents|install-skills|install|env-cleanup [--path ROOT]
19
+ ms-devtools setup --uid UID --branch BRANCH --base-dir DIR [--install] [--backend URL]
20
+
21
+ sync refreshes dependency files with the project SDK. Python: uv.lock, the environment and
22
+ requirements.txt. Node: package-lock.json, then npm ci. It does not commit, tag or push:
23
+ review the changes, then commit and push them yourself.
24
+ Project commands also accept --type and --backend. update-sdk and install-skills accept --dry-run.
25
+ All commands accept --json. No command accepts credentials as arguments.
26
+ skills-sync requires MainSequence MCP and reports unsupported.
27
+ open-folder and signed-terminal require a host with those native capabilities.
28
+ In Codex desktop, type /ms, select the ms slash-menu entry, then append COMMAND ... .
29
+ The trusted plugin hook executes the selected command before inference.
30
+ `;
31
+ function parseArguments(argv, cwd = process.cwd()) {
32
+ if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h')
33
+ return { json: argv.includes('--json'), help: true };
34
+ const action = argv[0];
35
+ const flags = {};
36
+ const boolean = new Set(['--json', '--check', '--dry-run', '--install']);
37
+ const valued = new Set(['--backend', '--path', '--type', '--uid', '--branch', '--base-dir', '--search', '--limit', '--offset', '--tool', '--arguments-file']);
38
+ for (let i = 1; i < argv.length; i++) {
39
+ const key = argv[i];
40
+ if (key === '--help' || key === '-h')
41
+ return { json: argv.includes('--json'), help: true };
42
+ if (action === 'sync' && ['--message', '-m', '--dry-run'].includes(key)) {
43
+ throw new models_1.ToolingError('INVALID_REQUEST', `sync no longer takes ${key}: it refreshes dependency files and does not commit, tag or push. Commit and push the changes yourself.`);
44
+ }
45
+ if (key in flags)
46
+ throw new models_1.ToolingError('INVALID_REQUEST', 'Duplicate command option.');
47
+ if (boolean.has(key))
48
+ flags[key] = true;
49
+ else if (valued.has(key)) {
50
+ const value = argv[++i];
51
+ if (value === undefined || !value.trim() || value.includes('\0'))
52
+ throw new models_1.ToolingError('INVALID_REQUEST', 'A command option is missing its value.');
53
+ flags[key] = value;
54
+ }
55
+ else
56
+ throw new models_1.ToolingError('INVALID_REQUEST', 'Unknown command option or positional argument. Run --help.');
57
+ }
58
+ const global = ['--backend', '--json'];
59
+ const project = ['--path', '--type'];
60
+ const allowed = action === 'mcp-discover' ? [...global, '--search', '--limit', '--offset']
61
+ : action === 'mcp-list' ? global
62
+ : ['mcp-add', 'mcp-remove', 'mcp-tools'].includes(action) ? [...global, '--uid']
63
+ : action === 'mcp-call' ? [...global, '--uid', '--tool', '--arguments-file']
64
+ : ['login', 'logout', 'refresh-token', 'repositories'].includes(action) ? global
65
+ : action === 'auth-status' ? [...global, '--check']
66
+ : action === 'setup' ? [...global, '--uid', '--branch', '--base-dir', '--install']
67
+ : [...global, ...project, ...(['update-sdk', 'install-skills'].includes(action) ? ['--dry-run'] : [])];
68
+ if (Object.keys(flags).some(key => !allowed.includes(key)))
69
+ throw new models_1.ToolingError('INVALID_REQUEST', 'This option does not apply to the selected action.');
70
+ const backend = typeof flags['--backend'] === 'string' ? flags['--backend'] : undefined;
71
+ const common = { backend };
72
+ let request;
73
+ if (['mcp-discover', 'mcp-list', 'mcp-add', 'mcp-remove', 'mcp-tools', 'mcp-call'].includes(action)) {
74
+ let args = {};
75
+ if (typeof flags['--arguments-file'] === 'string') {
76
+ try {
77
+ if ((0, node_fs_1.statSync)(flags['--arguments-file']).size > 1_000_000)
78
+ throw new Error();
79
+ args = JSON.parse((0, node_fs_1.readFileSync)(flags['--arguments-file'], 'utf8'));
80
+ if (!args || typeof args !== 'object' || Array.isArray(args))
81
+ throw new Error();
82
+ }
83
+ catch {
84
+ throw new models_1.ToolingError('INVALID_REQUEST', 'The arguments file must contain a JSON object of at most 1 MB.');
85
+ }
86
+ }
87
+ request = { action, ...common, ...(flags['--uid'] ? { releaseUid: flags['--uid'] } : {}),
88
+ ...(flags['--search'] !== undefined ? { search: flags['--search'] } : {}),
89
+ ...(flags['--limit'] !== undefined ? { limit: Number(flags['--limit']) } : {}),
90
+ ...(flags['--offset'] !== undefined ? { offset: Number(flags['--offset']) } : {}),
91
+ ...(action === 'mcp-call' ? { tool: flags['--tool'], arguments: args } : {}) };
92
+ }
93
+ else if (['login', 'logout', 'refresh-token', 'repositories'].includes(action))
94
+ request = { action, ...common };
95
+ else if (action === 'auth-status')
96
+ request = { action, ...common, check: Boolean(flags['--check']) };
97
+ else if (action === 'setup')
98
+ request = { action, ...common, repositoryUid: flags['--uid'], branch: flags['--branch'], baseDir: flags['--base-dir'], install: Boolean(flags['--install']) };
99
+ else
100
+ request = { action, ...common, root: flags['--path'] || cwd, projectType: flags['--type'], dryRun: Boolean(flags['--dry-run']) };
101
+ return { request, json: Boolean(flags['--json']) };
102
+ }
103
+ /** Tokenize explicit hook commands without evaluating a shell, substitutions, or expansions. */
104
+ function tokenizeCommand(text) {
105
+ if (/[\r\n\0]/.test(text))
106
+ throw new models_1.ToolingError('INVALID_REQUEST', 'MainSequence commands must be a single line.');
107
+ const words = [];
108
+ let word = '', quote = '', started = false;
109
+ for (let i = 0; i < text.length; i++) {
110
+ const character = text[i];
111
+ if (quote) {
112
+ if (character === quote)
113
+ quote = '';
114
+ else if (character === '\\' && quote === '"' && ['"', '\\'].includes(text[i + 1]))
115
+ word += text[++i];
116
+ else
117
+ word += character;
118
+ }
119
+ else if (character === "'" || character === '"') {
120
+ quote = character;
121
+ started = true;
122
+ }
123
+ else if (/\s/.test(character)) {
124
+ if (started) {
125
+ words.push(word);
126
+ word = '';
127
+ started = false;
128
+ }
129
+ }
130
+ else if (';&|<>`'.includes(character))
131
+ throw new models_1.ToolingError('INVALID_REQUEST', 'Shell operators are not supported in MainSequence commands.');
132
+ else {
133
+ word += character;
134
+ started = true;
135
+ }
136
+ }
137
+ if (quote)
138
+ throw new models_1.ToolingError('INVALID_REQUEST', 'A command argument has an unclosed quote.');
139
+ if (started)
140
+ words.push(word);
141
+ return words;
142
+ }
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.resolveCodeRepositoryCli = resolveCodeRepositoryCli;
37
+ exports.buildCodeRepositoryCliInvocations = buildCodeRepositoryCliInvocations;
38
+ exports.codeRepositoryActionPreview = codeRepositoryActionPreview;
39
+ exports.sanitizeCommitMessage = sanitizeCommitMessage;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ function resolveCodeRepositoryCli(codeRepositoryRoot, codeRepositoryType) {
43
+ if (codeRepositoryType === 'python') {
44
+ const executable = process.platform === 'win32'
45
+ ? path.join(codeRepositoryRoot, '.venv', 'Scripts', 'mainsequence.exe')
46
+ : path.join(codeRepositoryRoot, '.venv', 'bin', 'mainsequence');
47
+ return {
48
+ codeRepositoryType,
49
+ commandName: 'mainsequence',
50
+ packageName: 'mainsequence',
51
+ executable,
52
+ prefixArgs: [],
53
+ available: isFile(executable),
54
+ bootstrapAction: 'buildLocalVenv',
55
+ bootstrapLabel: 'Build Local .venv',
56
+ missingMessage: 'The CodeRepository-local mainsequence CLI was not found. Build the local .venv or run uv sync.'
57
+ };
58
+ }
59
+ const packageRoot = path.join(codeRepositoryRoot, 'node_modules', '@dev-mainsequence', 'command-center-sdk');
60
+ const entrypoint = path.join(packageRoot, 'cli', 'command-center-sdk.mjs');
61
+ return {
62
+ codeRepositoryType,
63
+ commandName: 'command-center-sdk',
64
+ packageName: '@dev-mainsequence/command-center-sdk',
65
+ // The installed entry point has a Node shebang on POSIX. Windows needs the
66
+ // system Node executable because .mjs files are not directly executable.
67
+ executable: process.execPath,
68
+ prefixArgs: [entrypoint],
69
+ available: isFile(entrypoint),
70
+ bootstrapAction: 'installDependencies',
71
+ bootstrapLabel: 'Install Dependencies',
72
+ missingMessage: 'The CodeRepository-local command-center-sdk CLI was not found. Run npm ci in the repository root.'
73
+ };
74
+ }
75
+ function buildCodeRepositoryCliInvocations(cli, action,
76
+ /** @deprecated `commitMessage` is ignored: sync takes no commit message. */
77
+ _options = {}) {
78
+ if (!cli.available)
79
+ return [];
80
+ const args = [];
81
+ if (action === 'sdkStatus' && cli.codeRepositoryType === 'vite_react') {
82
+ args.push(['application', 'sdk-status', '--path', '.', '--json']);
83
+ }
84
+ if (action === 'updateSdk') {
85
+ args.push(cli.codeRepositoryType === 'python'
86
+ ? ['code-repository', 'update-sdk', '--path', '.']
87
+ : ['application', 'update-sdk', '--path', '.']);
88
+ }
89
+ if (action === 'sync') {
90
+ // Python: uv lock, uv sync and the locked requirements.txt export. Node: package-lock.json and npm ci.
91
+ // No commit message, Git command or backend request; the person commits and pushes.
92
+ args.push(['code-repository', 'sync', '--path', '.']);
93
+ }
94
+ if (action === 'skills') {
95
+ if (cli.codeRepositoryType === 'python') {
96
+ args.push(['code-repository', 'update-agent-skills', '--path', '.'], ['code-repository', 'update', 'AGENTS.md', '--path', '.']);
97
+ }
98
+ else {
99
+ args.push(['skills', 'sync', '--path', '.']);
100
+ }
101
+ }
102
+ return args.map((commandArgs) => ({
103
+ executable: cli.executable,
104
+ args: [...cli.prefixArgs, ...commandArgs],
105
+ display: formatDisplayCommand(cli.commandName, commandArgs)
106
+ }));
107
+ }
108
+ function codeRepositoryActionPreview(codeRepositoryType, action,
109
+ /** @deprecated Ignored: sync takes no commit message. */
110
+ _commitMessage) {
111
+ const commandName = codeRepositoryType === 'python' ? 'mainsequence' : 'command-center-sdk';
112
+ const fakeCli = {
113
+ codeRepositoryType,
114
+ commandName,
115
+ packageName: codeRepositoryType === 'python' ? 'mainsequence' : '@dev-mainsequence/command-center-sdk',
116
+ executable: commandName,
117
+ prefixArgs: [],
118
+ available: true,
119
+ bootstrapAction: codeRepositoryType === 'python' ? 'buildLocalVenv' : 'installDependencies',
120
+ bootstrapLabel: codeRepositoryType === 'python' ? 'Build Local .venv' : 'Install Dependencies',
121
+ missingMessage: ''
122
+ };
123
+ return buildCodeRepositoryCliInvocations(fakeCli, action).map(item => item.display);
124
+ }
125
+ /** @deprecated Sync takes no commit message. Kept for existing callers; nothing in this package uses it. */
126
+ function sanitizeCommitMessage(message) {
127
+ return String(message || '').replace(/[\r\n]+/g, ' ').trim();
128
+ }
129
+ function formatDisplayCommand(command, args) {
130
+ return [command, ...args].map(quoteDisplayArgument).join(' ');
131
+ }
132
+ function quoteDisplayArgument(value) {
133
+ if (/^[A-Za-z0-9_./:@+-]+$/.test(value))
134
+ return value;
135
+ return JSON.stringify(value);
136
+ }
137
+ function isFile(value) {
138
+ try {
139
+ return fs.statSync(value).isFile();
140
+ }
141
+ catch {
142
+ return false;
143
+ }
144
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.normalizeBackendUrl = normalizeBackendUrl;
37
+ const net = __importStar(require("net"));
38
+ function normalizeBackendUrl(input) {
39
+ const raw = String(input || '').trim();
40
+ if (!raw)
41
+ return '';
42
+ if (raw.includes('://'))
43
+ return raw.replace(/\/+$/, '');
44
+ const host = raw.split('/', 1)[0].split(':', 1)[0].replace(/^\[|\]$/g, '').toLowerCase();
45
+ const localHost = host === 'localhost' || host === '0.0.0.0' || isPrivateIp(host);
46
+ return `${localHost ? 'http' : 'https'}://${raw}`.replace(/\/+$/, '');
47
+ }
48
+ function isPrivateIp(host) {
49
+ const kind = net.isIP(host);
50
+ if (kind === 4) {
51
+ const parts = host.split('.').map(Number);
52
+ return parts[0] === 0 || parts[0] === 10 || parts[0] === 127 ||
53
+ (parts[0] === 169 && parts[1] === 254) ||
54
+ (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) ||
55
+ (parts[0] === 192 && parts[1] === 168);
56
+ }
57
+ if (kind === 6) {
58
+ const normalized = host.toLowerCase();
59
+ return normalized === '::' || normalized === '::1' || normalized.startsWith('fc') ||
60
+ normalized.startsWith('fd') || normalized.startsWith('fe8') || normalized.startsWith('fe9') ||
61
+ normalized.startsWith('fea') || normalized.startsWith('feb');
62
+ }
63
+ return false;
64
+ }