@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,338 @@
1
+ "use strict";
2
+ /**
3
+ * The developer's Main Sequence session on this machine.
4
+ *
5
+ * One session per backend lives in the operating system credential store. The Main Sequence Python
6
+ * CLI and the Command Center SDK CLI read and write the same record, so a login made here, or with
7
+ * either CLI, serves all three. The record and the way each store is reached must stay equal to
8
+ * theirs.
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.ForeignEntryError = exports.CredentialStoreError = exports.SESSION_SERVICE = void 0;
45
+ exports.openCredentialStore = openCredentialStore;
46
+ exports.sessionAccount = sessionAccount;
47
+ exports.readSession = readSession;
48
+ exports.saveSession = saveSession;
49
+ exports.clearSession = clearSession;
50
+ const child_process_1 = require("child_process");
51
+ const crypto = __importStar(require("crypto"));
52
+ const fs = __importStar(require("fs"));
53
+ const path = __importStar(require("path"));
54
+ const config_1 = require("./config.js");
55
+ exports.SESSION_SERVICE = 'MainSequenceCLI.auth';
56
+ const SESSION_RECORD_VERSION = 1;
57
+ // Old versions of the Python CLI wrote one account for every backend.
58
+ const UNSCOPED_ACCOUNT = 'default';
59
+ const SECURITY_PROGRAM = '/usr/bin/security';
60
+ const SECURITY_ITEM_NOT_FOUND = 44;
61
+ const STORE_TIMEOUT_MS = 10_000;
62
+ // `security -i` reads one command per line into a 4,096-character buffer and cuts a longer line:
63
+ // the first part runs with a truncated secret and the rest runs as another command.
64
+ const SECURITY_STDIN_LINE_LIMIT = 4000;
65
+ const SAFE_ENTRY_NAME = /^[A-Za-z0-9._@-]+$/;
66
+ // The comment attribute of every Keychain entry the Main Sequence tools write. Attributes are
67
+ // readable without consent, the secret is not: the mark tells, before the secret is asked for, that
68
+ // `security` wrote the entry and may read it without a dialog.
69
+ const SECURITY_ENTRY_MARK = 'MainSequenceCLI.session.v1';
70
+ const SECURITY_COMMENT_ATTRIBUTE = /^\s*"icmt"<blob>="([^"]*)"\s*$/m;
71
+ // The Python CLI keeps its Secret Service item under this attribute and replaces only an item
72
+ // that carries it. Writing the same attributes makes every tool replace one item in place.
73
+ const SECRET_SERVICE_APPLICATION = 'Python keyring library';
74
+ /** The credential store exists and could not be read or written. */
75
+ class CredentialStoreError extends Error {
76
+ constructor(message) {
77
+ super(message);
78
+ this.name = 'CredentialStoreError';
79
+ }
80
+ }
81
+ exports.CredentialStoreError = CredentialStoreError;
82
+ /** The entry was written by a program this one may not ask for it without a consent dialog. */
83
+ class ForeignEntryError extends CredentialStoreError {
84
+ constructor() {
85
+ super('The Keychain entry was written by another version of the Main Sequence tools. Asking for it would show a consent dialog in every process, so it is not read.');
86
+ this.name = 'ForeignEntryError';
87
+ }
88
+ }
89
+ exports.ForeignEntryError = ForeignEntryError;
90
+ function runProgram(run, program, args, input) {
91
+ const result = run(program, args, {
92
+ input: input ?? '',
93
+ encoding: 'utf8',
94
+ timeout: STORE_TIMEOUT_MS,
95
+ windowsHide: true
96
+ });
97
+ if (result.error) {
98
+ throw new CredentialStoreError(result.error.code === 'ETIMEDOUT'
99
+ ? `The credential store did not answer within ${STORE_TIMEOUT_MS / 1000} seconds. It was waiting for the user: the entry belongs to another program, or the store is locked.`
100
+ : `The credential store program could not be run (${result.error.code || result.error.name}).`);
101
+ }
102
+ return result;
103
+ }
104
+ function decodeSecuritySecret(output) {
105
+ // `security -w` prints printable ASCII as it is and anything else as hexadecimal. The record is
106
+ // JSON, so a value made only of hexadecimal digits is the second form.
107
+ const value = output.endsWith('\n') ? output.slice(0, -1) : output;
108
+ if (value && value.length % 2 === 0 && /^[0-9a-fA-F]+$/.test(value)) {
109
+ const decoded = Buffer.from(value, 'hex').toString('utf8');
110
+ if (!decoded.includes('�'))
111
+ return decoded;
112
+ }
113
+ return value;
114
+ }
115
+ /**
116
+ * The login Keychain through Apple's `security` program, which is one program for every runtime.
117
+ *
118
+ * Asking `security` for the secret of an entry that another program wrote shows a consent dialog,
119
+ * in every process that starts. The secret is therefore asked for only when the entry carries the
120
+ * tools' mark. Any other entry is left alone until a login replaces it.
121
+ */
122
+ function createMacOsKeychain(run) {
123
+ const readable = new Set();
124
+ const key = (service, account) => `${service}\n${account}`;
125
+ function remove(service, account) {
126
+ const result = runProgram(run, SECURITY_PROGRAM, ['delete-generic-password', '-s', service, '-a', account]);
127
+ readable.delete(key(service, account));
128
+ if (result.status === SECURITY_ITEM_NOT_FOUND)
129
+ return false;
130
+ if (result.status !== 0) {
131
+ throw new CredentialStoreError(`The Keychain entry could not be deleted (security exit ${result.status}).`);
132
+ }
133
+ return true;
134
+ }
135
+ return {
136
+ name: 'macOS Keychain',
137
+ read(service, account) {
138
+ readable.delete(key(service, account));
139
+ // Attributes only: this shows no dialog, whoever wrote the entry.
140
+ const inspected = runProgram(run, SECURITY_PROGRAM, ['find-generic-password', '-s', service, '-a', account]);
141
+ if (inspected.status === SECURITY_ITEM_NOT_FOUND)
142
+ return null;
143
+ if (inspected.status !== 0) {
144
+ throw new CredentialStoreError(`The Keychain entry could not be inspected (security exit ${inspected.status}).`);
145
+ }
146
+ if (SECURITY_COMMENT_ATTRIBUTE.exec(inspected.stdout)?.[1] !== SECURITY_ENTRY_MARK) {
147
+ throw new ForeignEntryError();
148
+ }
149
+ const result = runProgram(run, SECURITY_PROGRAM, ['find-generic-password', '-s', service, '-a', account, '-w']);
150
+ if (result.status === SECURITY_ITEM_NOT_FOUND)
151
+ return null;
152
+ if (result.status !== 0) {
153
+ throw new CredentialStoreError(`The Keychain entry could not be read (security exit ${result.status}).`);
154
+ }
155
+ readable.add(key(service, account));
156
+ return decodeSecuritySecret(result.stdout);
157
+ },
158
+ write(service, account, secret) {
159
+ if (!SAFE_ENTRY_NAME.test(service) || !SAFE_ENTRY_NAME.test(account)) {
160
+ throw new CredentialStoreError('Unsupported Keychain entry name.');
161
+ }
162
+ const line = `add-generic-password -U -s ${service} -a ${account} -j ${SECURITY_ENTRY_MARK} -X ${Buffer.from(secret, 'utf8').toString('hex')}\n`;
163
+ if (line.length > SECURITY_STDIN_LINE_LIMIT) {
164
+ throw new CredentialStoreError('The session record is too long to store through the Keychain helper.');
165
+ }
166
+ // An entry this process read is updated in place, so another process never finds it
167
+ // missing. Any other entry is removed first: updating one that another program created
168
+ // would wait for the user's consent, and removing it does not.
169
+ if (readable.has(key(service, account))) {
170
+ try {
171
+ if (runProgram(run, SECURITY_PROGRAM, ['-i'], line).status === 0)
172
+ return;
173
+ }
174
+ catch {
175
+ // Fall through to remove and add.
176
+ }
177
+ }
178
+ remove(service, account);
179
+ // Never report this call's output: on a parse error it echoes the line.
180
+ const result = runProgram(run, SECURITY_PROGRAM, ['-i'], line);
181
+ if (result.status !== 0) {
182
+ throw new CredentialStoreError(`The Keychain entry could not be written (security exit ${result.status}).`);
183
+ }
184
+ readable.add(key(service, account));
185
+ },
186
+ remove
187
+ };
188
+ }
189
+ /** Secret Service through `secret-tool`, the program the desktop's libsecret ships. */
190
+ function createSecretService(run, program) {
191
+ function call(args, input) {
192
+ const result = runProgram(run, program, args, input);
193
+ // `secret-tool` exits 1 both for "no such item" and for an unreachable service. Only the
194
+ // second one says something on standard error.
195
+ const complaint = (result.stderr || '').trim().split('\n')[0] || '';
196
+ if (result.status !== 0 && complaint) {
197
+ throw new CredentialStoreError(`Secret Service is not available: ${complaint}`);
198
+ }
199
+ return result;
200
+ }
201
+ return {
202
+ name: 'Secret Service',
203
+ read(service, account) {
204
+ // The exact item the tools write, and then any item another program stored for the entry.
205
+ for (const extra of [['application', SECRET_SERVICE_APPLICATION], []]) {
206
+ const result = call(['lookup', 'service', service, 'username', account, ...extra]);
207
+ if (result.status === 0 && result.stdout)
208
+ return result.stdout;
209
+ }
210
+ return null;
211
+ },
212
+ write(service, account, secret) {
213
+ const result = call([
214
+ 'store',
215
+ `--label=Password for '${account}' on '${service}'`,
216
+ 'service',
217
+ service,
218
+ 'username',
219
+ account,
220
+ 'application',
221
+ SECRET_SERVICE_APPLICATION
222
+ ], secret);
223
+ if (result.status !== 0) {
224
+ throw new CredentialStoreError(`The Secret Service entry could not be written (secret-tool exit ${result.status}).`);
225
+ }
226
+ },
227
+ remove(service, account) {
228
+ return call(['clear', 'service', service, 'username', account]).status === 0;
229
+ }
230
+ };
231
+ }
232
+ function isExecutableFile(candidate) {
233
+ try {
234
+ fs.accessSync(candidate, fs.constants.X_OK);
235
+ return true;
236
+ }
237
+ catch {
238
+ return false;
239
+ }
240
+ }
241
+ /**
242
+ * This system's credential store, or null when the extension can reach none. macOS and Linux each
243
+ * use one named store, reached through a program of the system, so every runtime on the machine
244
+ * sees the same entry.
245
+ */
246
+ function openCredentialStore(options = {}) {
247
+ const platform = options.platform ?? process.platform;
248
+ const env = options.env ?? process.env;
249
+ const run = options.run ?? child_process_1.spawnSync;
250
+ const isExecutable = options.isExecutable ?? isExecutableFile;
251
+ if (platform === 'darwin') {
252
+ return isExecutable(SECURITY_PROGRAM) ? createMacOsKeychain(run) : null;
253
+ }
254
+ if (platform === 'linux') {
255
+ for (const directory of (env.PATH || '').split(path.delimiter)) {
256
+ if (!directory)
257
+ continue;
258
+ const candidate = path.join(directory, 'secret-tool');
259
+ if (isExecutable(candidate))
260
+ return createSecretService(run, candidate);
261
+ }
262
+ }
263
+ return null;
264
+ }
265
+ function requireBackend(backend) {
266
+ const normalized = (0, config_1.normalizeBackendUrl)(backend);
267
+ if (!normalized)
268
+ throw new Error('MainSequence backend URL is required.');
269
+ return normalized;
270
+ }
271
+ /** The account that holds the session of one backend. Every tool computes it the same way. */
272
+ function sessionAccount(backend) {
273
+ const digest = crypto.createHash('sha256').update(requireBackend(backend), 'utf8').digest('hex');
274
+ return `${UNSCOPED_ACCOUNT}.${digest.slice(0, 16)}`;
275
+ }
276
+ function parseRecord(raw, backend) {
277
+ let record;
278
+ try {
279
+ record = JSON.parse(raw);
280
+ }
281
+ catch {
282
+ throw new CredentialStoreError('The saved session record is not valid.');
283
+ }
284
+ if (!record || typeof record !== 'object' || Array.isArray(record)) {
285
+ throw new CredentialStoreError('The saved session record is not valid.');
286
+ }
287
+ // `v` is written, not checked: the Python and Command Center CLIs read a record by its fields,
288
+ // and the three readers must accept the same records.
289
+ // A record written before the backend was part of it carries none.
290
+ const recordedBackend = typeof record.backend === 'string' ? record.backend.trim() : '';
291
+ if (recordedBackend && (0, config_1.normalizeBackendUrl)(recordedBackend) !== requireBackend(backend))
292
+ return null;
293
+ const session = {
294
+ username: typeof record.username === 'string' ? record.username : '',
295
+ access: typeof record.access === 'string' ? record.access : '',
296
+ refresh: typeof record.refresh === 'string' ? record.refresh : ''
297
+ };
298
+ return session.access || session.refresh ? session : null;
299
+ }
300
+ /** Read the saved session of one backend, or null. */
301
+ function readSession(store, backend) {
302
+ for (const account of [sessionAccount(backend), UNSCOPED_ACCOUNT]) {
303
+ const raw = store.read(exports.SESSION_SERVICE, account);
304
+ if (!raw)
305
+ continue;
306
+ const session = parseRecord(raw, backend);
307
+ if (session)
308
+ return session;
309
+ }
310
+ return null;
311
+ }
312
+ /** Save the session of one backend. */
313
+ function saveSession(store, backend, session) {
314
+ const json = JSON.stringify({
315
+ v: SESSION_RECORD_VERSION,
316
+ backend: requireBackend(backend),
317
+ username: session.username || '',
318
+ access: session.access || '',
319
+ refresh: session.refresh || ''
320
+ });
321
+ // ASCII only, as the Python CLI writes it, so every store returns the same bytes.
322
+ const ascii = json.replace(/[\u007f-￿]/g, character => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`);
323
+ store.write(exports.SESSION_SERVICE, sessionAccount(backend), ascii);
324
+ }
325
+ /**
326
+ * Remove the saved session of one backend. Missing state is success.
327
+ *
328
+ * The legacy unscoped entry goes too, unread, as the Python and Command Center CLIs remove it:
329
+ * deleting needs no consent, while reading an entry another program wrote is refused on macOS
330
+ * and would make the logout fail.
331
+ */
332
+ function clearSession(store, backend) {
333
+ let removed = false;
334
+ for (const account of [sessionAccount(backend), UNSCOPED_ACCOUNT]) {
335
+ removed = store.remove(exports.SESSION_SERVICE, account) || removed;
336
+ }
337
+ return removed;
338
+ }