@ravefox/space 0.2.0 → 0.3.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.
Files changed (163) hide show
  1. package/LICENSE +21 -203
  2. package/README.md +89 -26
  3. package/bin/space.js +106 -0
  4. package/lib/agents.js +119 -0
  5. package/lib/credentials.js +95 -0
  6. package/lib/daemon.js +294 -0
  7. package/lib/harness.js +166 -0
  8. package/lib/http.js +101 -0
  9. package/lib/login.js +68 -0
  10. package/lib/mcp.js +34 -0
  11. package/lib/runtime.js +241 -0
  12. package/lib/store.js +96 -0
  13. package/lib/tools.js +93 -0
  14. package/package.json +28 -40
  15. package/NOTICE +0 -44
  16. package/assets/webhook-sdk/python/bot.py +0 -63
  17. package/assets/webhook-sdk/python/commonly.py +0 -232
  18. package/node_modules/@commonlyai/seats/README.md +0 -25
  19. package/node_modules/@commonlyai/seats/package.json +0 -22
  20. package/node_modules/@commonlyai/seats/src/adapters/hermes.js +0 -264
  21. package/node_modules/@commonlyai/seats/src/adapters/stub.js +0 -24
  22. package/node_modules/@commonlyai/seats/src/contract.js +0 -48
  23. package/node_modules/@commonlyai/seats/src/failures.js +0 -44
  24. package/node_modules/@commonlyai/seats/src/harness-plan.js +0 -329
  25. package/node_modules/@commonlyai/seats/src/index.js +0 -22
  26. package/node_modules/@commonlyai/seats/src/process-group.js +0 -269
  27. package/node_modules/@commonlyai/seats/src/record/format.js +0 -737
  28. package/node_modules/@commonlyai/seats/src/record/otel.js +0 -238
  29. package/node_modules/@commonlyai/seats/src/runtime-observation.js +0 -29
  30. package/node_modules/@commonlyai/seats/src/seat-model.js +0 -17
  31. package/node_modules/@commonlyai/seats/src/supervisor.js +0 -308
  32. package/node_modules/@commonlyai/seats/src/trace/events.js +0 -46
  33. package/node_modules/@commonlyai/seats/src/trace/store.js +0 -276
  34. package/node_modules/@commonlyai/seats/src/turn-processes.js +0 -226
  35. package/node_modules/@commonlyai/seats/src/upstream-refusal.js +0 -418
  36. package/node_modules/@commonlyai/seats/src/valet/env.js +0 -201
  37. package/node_modules/@commonlyai/seats/src/valet/grant.js +0 -123
  38. package/node_modules/@commonlyai/seats/src/valet/permissions.js +0 -213
  39. package/node_modules/@commonlyai/seats/src/wire/provider-proxy.js +0 -529
  40. package/skills/commonly/SKILL.md +0 -233
  41. package/src/commands/agent.js +0 -6334
  42. package/src/commands/daemon.js +0 -933
  43. package/src/commands/dev.js +0 -153
  44. package/src/commands/doctor.js +0 -141
  45. package/src/commands/inbox.js +0 -330
  46. package/src/commands/login.js +0 -194
  47. package/src/commands/mcp.js +0 -56
  48. package/src/commands/pod.js +0 -120
  49. package/src/commands/task.js +0 -279
  50. package/src/commands/verify.js +0 -48
  51. package/src/index.js +0 -99
  52. package/src/lib/adapter-login.js +0 -76
  53. package/src/lib/adapters/broker-bridge-entry.js +0 -76
  54. package/src/lib/adapters/claude.js +0 -2207
  55. package/src/lib/adapters/codex-broker-bridge.mjs +0 -298
  56. package/src/lib/adapters/codex.js +0 -1898
  57. package/src/lib/adapters/dsh.js +0 -513
  58. package/src/lib/adapters/goose.js +0 -510
  59. package/src/lib/adapters/hermes.js +0 -18
  60. package/src/lib/adapters/index.js +0 -68
  61. package/src/lib/adapters/opencode.js +0 -1681
  62. package/src/lib/adapters/pi-commonly-mcp.mjs +0 -71
  63. package/src/lib/adapters/pi-mcp-client.mjs +0 -617
  64. package/src/lib/adapters/pi-shell-env.mjs +0 -41
  65. package/src/lib/adapters/pi.js +0 -1025
  66. package/src/lib/adapters/qwen.js +0 -523
  67. package/src/lib/adapters/seat-model.js +0 -8
  68. package/src/lib/adapters/stub.js +0 -1
  69. package/src/lib/adapters/tested-version.js +0 -106
  70. package/src/lib/api.js +0 -205
  71. package/src/lib/claim-outcome.js +0 -128
  72. package/src/lib/claimed-work-gate.js +0 -73
  73. package/src/lib/cli-hint.js +0 -33
  74. package/src/lib/commit-attribution.js +0 -391
  75. package/src/lib/config.js +0 -155
  76. package/src/lib/content-refusal.js +0 -94
  77. package/src/lib/credential-file.js +0 -208
  78. package/src/lib/daemon-agent-states.js +0 -95
  79. package/src/lib/daemon-hint.js +0 -18
  80. package/src/lib/daemon-logs.js +0 -43
  81. package/src/lib/daemon-service-windows.js +0 -263
  82. package/src/lib/daemon-service.js +0 -486
  83. package/src/lib/daemon-state.js +0 -84
  84. package/src/lib/daemon-store.js +0 -119
  85. package/src/lib/daemon-supervisor.js +0 -629
  86. package/src/lib/daemon-watch.js +0 -115
  87. package/src/lib/declared-mcp-guard.js +0 -284
  88. package/src/lib/default-environment.js +0 -207
  89. package/src/lib/device-login.js +0 -126
  90. package/src/lib/digest-frame.js +0 -151
  91. package/src/lib/doctor.js +0 -291
  92. package/src/lib/egress-proxy.js +0 -160
  93. package/src/lib/enforcement.js +0 -946
  94. package/src/lib/environment.js +0 -681
  95. package/src/lib/first-output-watchdog.js +0 -191
  96. package/src/lib/git-readonly-server.js +0 -138
  97. package/src/lib/git-readonly.js +0 -463
  98. package/src/lib/git-remote-commonly.mjs +0 -100
  99. package/src/lib/grant-broker-guard.js +0 -345
  100. package/src/lib/hooks-config.js +0 -238
  101. package/src/lib/host-gh-broker.js +0 -152
  102. package/src/lib/host-gh-shim.mjs +0 -98
  103. package/src/lib/inbox.js +0 -483
  104. package/src/lib/instance-proxy.js +0 -76
  105. package/src/lib/machine-host.js +0 -29
  106. package/src/lib/mcp-credential-delivery.js +0 -121
  107. package/src/lib/mcp-first-run.js +0 -50
  108. package/src/lib/mcp-home.js +0 -644
  109. package/src/lib/mcp-ready.js +0 -68
  110. package/src/lib/mcp-server-version.js +0 -123
  111. package/src/lib/mcp-token.js +0 -36
  112. package/src/lib/mcp-warm-child.mjs +0 -412
  113. package/src/lib/memory-bridge.js +0 -191
  114. package/src/lib/memory-import.js +0 -156
  115. package/src/lib/missing-harness.js +0 -31
  116. package/src/lib/model-unavailable.js +0 -154
  117. package/src/lib/native-runtime-inventory.js +0 -40
  118. package/src/lib/outsider-grant-mcp.js +0 -82
  119. package/src/lib/own-key-launch.js +0 -42
  120. package/src/lib/pod-catch-up.js +0 -55
  121. package/src/lib/pod-focus.js +0 -224
  122. package/src/lib/pod-tail.js +0 -68
  123. package/src/lib/poll-liveness.js +0 -103
  124. package/src/lib/poll-retry.js +0 -81
  125. package/src/lib/poller.js +0 -200
  126. package/src/lib/provider-proxy.js +0 -42
  127. package/src/lib/public-seat-mcp-flag.js +0 -36
  128. package/src/lib/public-tool-error.js +0 -60
  129. package/src/lib/public-withheld-tools.js +0 -48
  130. package/src/lib/reply-receipt.js +0 -55
  131. package/src/lib/resolve-on-path.js +0 -22
  132. package/src/lib/runtime-inventory.js +0 -56
  133. package/src/lib/runtime-observation.js +0 -37
  134. package/src/lib/sandbox/bwrap.js +0 -275
  135. package/src/lib/sandbox/codex-perimeter.js +0 -16
  136. package/src/lib/sandbox/host-check.js +0 -268
  137. package/src/lib/sandbox/mode.js +0 -49
  138. package/src/lib/sandbox/opencode-loopback.mjs +0 -68
  139. package/src/lib/sandbox/seatbelt.js +0 -684
  140. package/src/lib/sandbox/turn-dir.js +0 -56
  141. package/src/lib/sandbox/windows-unsandboxed.js +0 -183
  142. package/src/lib/sandbox/workspace-git-config.js +0 -243
  143. package/src/lib/sandbox/workspace-pin.js +0 -158
  144. package/src/lib/seat-runtime-controls.js +0 -61
  145. package/src/lib/seat-stop.js +0 -97
  146. package/src/lib/server-skills.js +0 -32
  147. package/src/lib/session-recycle.js +0 -219
  148. package/src/lib/session-store.js +0 -215
  149. package/src/lib/skill-prompt.js +0 -74
  150. package/src/lib/skills-import.js +0 -113
  151. package/src/lib/spawn-credential.js +0 -402
  152. package/src/lib/spawn-retry.js +0 -311
  153. package/src/lib/stable-install.js +0 -176
  154. package/src/lib/supervised-turn.js +0 -352
  155. package/src/lib/task.js +0 -235
  156. package/src/lib/turn-failure-notice.js +0 -177
  157. package/src/lib/unconfined-seat-guard.js +0 -94
  158. package/src/lib/update-check.js +0 -192
  159. package/src/lib/upstream-refusal.js +0 -1
  160. package/src/lib/webhook-server.js +0 -109
  161. package/src/lib/windows-command.js +0 -393
  162. package/src/lib/workspace-git.js +0 -87
  163. package/src/lib/workspace-trust.js +0 -162
@@ -1,48 +0,0 @@
1
- /** Repository verification entry point; scenario implementations live in backend. */
2
- import { existsSync } from 'fs';
3
- import { resolve, join } from 'path';
4
- import { spawnSync } from 'child_process';
5
-
6
- export const SCENARIOS = {
7
- wave1: '__tests__/service/verify.wave1.test.js',
8
- 'connector-grant': '__tests__/service/verify.connectorGrant.test.js',
9
- };
10
- const testDatabase = (name) => /^(test|verify)([-_0-9]|$)|[-_]test$/.test(name || '');
11
-
12
- export const verificationPlan = (scenario, repo, env = process.env) => {
13
- const suite = SCENARIOS[scenario];
14
- if (!suite) throw new Error(`Unknown scenario: ${scenario}. Choose ${Object.keys(SCENARIOS).join(', ')}.`);
15
- let mongoDatabase = '';
16
- // MongoDB authorities can contain comma-separated seed hosts, unlike WHATWG URLs.
17
- const mongoPath = /^mongodb(?:\+srv)?:\/\/[^/?#]+\/([^?#]*)/.exec(env.MONGO_URI || '');
18
- try { mongoDatabase = decodeURIComponent(mongoPath?.[1] || ''); } catch { /* refused below */ }
19
- if (!env.PG_HOST || !testDatabase(env.PG_DATABASE) || !testDatabase(mongoDatabase)) {
20
- throw new Error('Set MONGO_URI, PG_HOST and PG_DATABASE to dedicated test/verify databases. Fixture setup clears their data.');
21
- }
22
- const cwd = resolve(repo, 'backend');
23
- const runner = join(cwd, 'node_modules/jest/bin/jest.js');
24
- if (!existsSync(join(cwd, suite)) || !existsSync(runner)) {
25
- throw new Error('Use --repo with a Space checkout containing the scenario and installed backend dependencies.');
26
- }
27
- return { command: process.execPath, args: [runner, '--runInBand', '--forceExit', '--runTestsByPath', suite,
28
- ...(env.COMMONLY_VERIFY_CACHE_DIR ? ['--cacheDirectory', env.COMMONLY_VERIFY_CACHE_DIR] : [])],
29
- options: { cwd, stdio: 'inherit', env: { ...env, INTEGRATION_TEST: 'true' } } };
30
- };
31
-
32
- export const registerVerify = (program, run = spawnSync) => {
33
- program.command('verify <scenario>')
34
- .description(`Run shared real-HTTP scenarios against isolated test databases (${Object.keys(SCENARIOS).join(', ')})`)
35
- .option('--repo <path>', 'Space checkout with backend test dependencies', process.cwd())
36
- .addHelpText('after', '\nUse Node 22 and explicit MONGO_URI, PG_HOST and PG_DATABASE. Fixture databases are cleared.\nRun inside the bounded test container when available; this command does not build images or contact a live instance.\n')
37
- .action((scenario, options) => {
38
- try {
39
- const plan = verificationPlan(scenario, options.repo);
40
- const result = run(plan.command, plan.args, plan.options);
41
- if (result.error) throw result.error;
42
- process.exitCode = result.status === null ? 1 : result.status;
43
- } catch (error) {
44
- console.error(error.message);
45
- process.exitCode = 1;
46
- }
47
- });
48
- };
package/src/index.js DELETED
@@ -1,99 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * @commonlyai/cli — the developer interface to CAP.
4
- *
5
- * Usage:
6
- * commonly login
7
- * commonly agent connect --name my-agent --port 3001
8
- * commonly pod send <podId> "hello"
9
- * commonly dev up
10
- */
11
-
12
- import { Command } from 'commander';
13
- import { readFileSync } from 'fs';
14
- import { join, dirname } from 'path';
15
- import { fileURLToPath } from 'url';
16
-
17
- import { registerLogin, registerWhoami } from './commands/login.js';
18
- import { registerInbox } from './commands/inbox.js';
19
- import { registerAgent } from './commands/agent.js';
20
- import { registerDaemon } from './commands/daemon.js';
21
- import { registerPod } from './commands/pod.js';
22
- import { registerDev } from './commands/dev.js';
23
- import { registerVerify } from './commands/verify.js';
24
- import { registerDoctor } from './commands/doctor.js';
25
- import { registerMcp } from './commands/mcp.js';
26
- import { registerTask } from './commands/task.js';
27
- import { CLI } from './lib/cli-hint.js';
28
-
29
- const __dirname = dirname(fileURLToPath(import.meta.url));
30
- const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
31
-
32
- const program = new Command();
33
-
34
- program
35
- .name('space')
36
- .description('The Space CLI — connect agents, manage pods, iterate fast')
37
- .version(pkg.version)
38
- .showHelpAfterError('(run `space --help` for usage)');
39
-
40
- // Auth
41
- registerLogin(program);
42
- registerWhoami(program);
43
-
44
- // An operator account's own attention queue (explicit token file, never the
45
- // saved login — see cli/src/lib/inbox.js)
46
- registerInbox(program);
47
-
48
- // Agent management
49
- registerAgent(program);
50
-
51
- // Local daemon lifecycle
52
- registerDaemon(program);
53
-
54
- // Pod management
55
- registerPod(program);
56
-
57
- // Local dev environment
58
- registerDev(program);
59
- registerVerify(program);
60
-
61
- // Install health: one line per check (TASK-660)
62
- registerDoctor(program);
63
- registerMcp(program);
64
-
65
- // The task board as an agent: list, create, claim, update, close (TASK-703)
66
- registerTask(program);
67
-
68
- // Each subcommand owns its own `--instance <url>` flag; a program-level
69
- // duplicate shadowed the subcommand value on commander v12, causing
70
- // `commonly login --instance …` to silently fall back to the default URL.
71
-
72
- program.addHelpText('after', `
73
- Quick start:
74
- $ ${CLI} login --instance https://api.commonly.me --key dev
75
- $ ${CLI} agent attach claude --pod <podId> --name my-claude
76
- $ ${CLI} agent run my-claude # Ctrl+C to stop
77
- $ ${CLI} agent detach my-claude # clean uninstall
78
- $ ${CLI} daemon register --name "My MacBook"
79
- $ ${CLI} daemon status
80
- $ ${CLI} doctor # check Node, install, login, daemon, adapters
81
- $ ${CLI} inbox list --token-file ~/.commonly/bin/ops-token # an operator's own queue
82
-
83
- Custom Python agent:
84
- $ ${CLI} agent init --language python --name research-bot --pod <podId>
85
- $ python3 research-bot.py
86
-
87
- Subcommand help:
88
- $ ${CLI} login --help
89
- $ ${CLI} agent attach --help
90
- $ ${CLI} agent run --help
91
- $ ${CLI} pod tail --help
92
-
93
- Note: --instance is a subcommand option, not a program option. It accepts
94
- either a saved key name ("dev", "local", "default") or a full URL.
95
-
96
- Docs: https://github.com/grootbro/space/blob/space/bootstrap/docs/cli/README.md
97
- `);
98
-
99
- program.parse(process.argv);
@@ -1,76 +0,0 @@
1
- import { spawn } from 'child_process';
2
-
3
- // Whether each installed CLI adapter is signed in, by the exit code of its own
4
- // status command. A stranger on a fresh computer otherwise learns it from a
5
- // failed @mention ("claude exited 1: Not logged in"), after setup said all was
6
- // well (Growth's F370 hand walk). Only the exit code is read: the commands'
7
- // output, which names the account, is never captured or printed.
8
- //
9
- // Both commands exit 1 when signed out (checked 2026-11-10 under an empty HOME:
10
- // `claude auth status --json`, `codex login status`). opencode is left out: its
11
- // `auth list` was not checked, and a guessed probe would cry wolf.
12
- export const LOGIN_STATUS_ARGS = {
13
- claude: ['auth', 'status', '--json'],
14
- codex: ['login', 'status'],
15
- };
16
-
17
- // Credentials a seat runs on without a CLI login, so the probe is skipped.
18
- // codex's are the ones codexAuthKind and the proxy route accept (adapters/
19
- // codex.js), even though `codex login status` exits 1 with only a key set.
20
- const KEY_ENV = {
21
- claude: ['ANTHROPIC_API_KEY'],
22
- codex: ['OPENAI_API_KEY', 'CODEX_API_KEY', 'CODEX_ACCESS_TOKEN'],
23
- };
24
- const envSet = (env, name) => typeof env[name] === 'string' && env[name].trim() !== '';
25
-
26
- // Both CLIs exit exactly 1 when signed out. Any other non-zero code (2 for an
27
- // unknown subcommand on an old CLI) is no verdict, not "signed out".
28
- const SIGNED_OUT_EXIT = 1;
29
-
30
- export const signedOutHint = (name) => `${name} is installed but not signed in; run \`${name}\` once to log in before adding an agent`;
31
-
32
- /**
33
- * The installed adapters that report signed out. `detect(name)` resolves truthy
34
- * when the CLI is installed; `runStatus(name, args)` resolves its exit code, or
35
- * null when it could not be run (timeout, spawn error), which is not a verdict.
36
- */
37
- export const signedOutAdapters = async ({ detect, runStatus, env = process.env, names = Object.keys(LOGIN_STATUS_ARGS) }) => {
38
- const out = [];
39
- for (const name of names) {
40
- if (!LOGIN_STATUS_ARGS[name] || (KEY_ENV[name] || []).some((key) => envSet(env, key))) continue;
41
- let installed = false;
42
- try { installed = Boolean(await detect(name)); } catch { /* not installed */ }
43
- if (!installed) continue;
44
- const code = await runStatus(name, LOGIN_STATUS_ARGS[name]).catch(() => null);
45
- if (code === SIGNED_OUT_EXIT) out.push(name);
46
- }
47
- return out;
48
- };
49
-
50
- /**
51
- * One harness's login, for the daemon's inventory (F458): true signed in (exit
52
- * 0), false signed out (exit 1), null when there is no verdict: no checked
53
- * probe for it, a key in the environment it runs on instead, or a probe that
54
- * couldn't run. Only the exit code is read, as above.
55
- */
56
- export const loginState = async (name, { runStatus = runLoginStatus, env = process.env } = {}) => {
57
- if (!LOGIN_STATUS_ARGS[name] || (KEY_ENV[name] || []).some((key) => envSet(env, key))) return null;
58
- const code = await runStatus(name, LOGIN_STATUS_ARGS[name]).catch(() => null);
59
- if (code === 0) return true;
60
- return code === SIGNED_OUT_EXIT ? false : null;
61
- };
62
-
63
- // The exit code of `<name> <args>`, with every stream discarded. null when it
64
- // cannot run or takes longer than `timeoutMs` (a CLI that opens a prompt).
65
- export const runLoginStatus = (name, args, { timeoutMs = 10_000, spawnFn = spawn } = {}) => new Promise((done) => {
66
- let child;
67
- try {
68
- child = spawnFn(name, args, { stdio: 'ignore', windowsHide: true, shell: process.platform === 'win32' });
69
- } catch {
70
- done(null);
71
- return;
72
- }
73
- const timer = setTimeout(() => { child.kill(); done(null); }, timeoutMs);
74
- child.on('error', () => { clearTimeout(timer); done(null); });
75
- child.on('exit', (code) => { clearTimeout(timer); done(typeof code === 'number' ? code : null); });
76
- });
@@ -1,76 +0,0 @@
1
- /**
2
- * The stdio server a harness runs to reach the grant broker (F175b, F175d).
3
- *
4
- * Codex, Qwen and Goose can't hold the broker's bearer token in an HTTP entry
5
- * without putting it somewhere the rule forbids: Codex reads it from an env
6
- * var, Qwen sends `--mcp-config` headers literally, and Goose fills a header
7
- * from its own environment or a literal in the recipe (measured on Qwen 0.26.2
8
- * and Goose 1.54.0). So each runs our bridge (codex-broker-bridge.mjs), told
9
- * the broker's url, its headers as templates and the path of this spawn's
10
- * credential file. None of those is a secret. The token stays in that one file,
11
- * and the bridge reads it for each request.
12
- *
13
- * Whether a seat may hold the broker at all was decided before the adapter
14
- * (grant-broker-guard.js). This decides whether THIS entry can be wired, and a
15
- * command the entry declares is never run.
16
- */
17
- import {
18
- BRIDGE_HEADERS_VAR, BRIDGE_PATH, BRIDGE_URL_VAR, CREDENTIAL_FILE_VAR,
19
- } from './codex-broker-bridge.mjs';
20
-
21
- // A server name becomes a config key or a tool-name prefix.
22
- export const SERVER_NAME = /^[A-Za-z0-9_-]+$/;
23
- // What fetch accepts in a header: Latin-1, no CR, LF or NUL.
24
- // eslint-disable-next-line no-control-regex
25
- const HEADER_TEXT = /^[^\r\n\0Ā-￿]*$/;
26
- const INSTANCE_PLACEHOLDER = /\$\{(?:COMMONLY_API_URL|COMMONLY_INSTANCE_URL)\}/g;
27
-
28
- /**
29
- * `{ command, args, env, readPath }`, or `{ skip }` with why it can't be wired.
30
- * `readPath` is the one file a jailed seat must be able to read: the bridge
31
- * imports node builtins only.
32
- */
33
- export const brokerBridgeEntry = (server, { instanceUrl, credentialFile, runtimeToken } = {}) => {
34
- if (!SERVER_NAME.test(server?.name || '')) return { skip: 'its name is not a plain config key' };
35
- if (!credentialFile) return { skip: 'there is no seat credential to read' };
36
- // Resolve the instance placeholders only: the token placeholder stays in the
37
- // header template and is filled by the bridge from the file.
38
- const resolve = (value) => String(value).replace(INSTANCE_PLACEHOLDER, (whole) => instanceUrl || whole);
39
- const resolvedUrl = resolve(server.url);
40
- const headers = Object.fromEntries(Object.entries(server.headers || {})
41
- .map(([name, value]) => [String(name), resolve(String(value))]));
42
- const carriesToken = (value) => !!runtimeToken && value.includes(runtimeToken);
43
- if (carriesToken(resolvedUrl)
44
- || Object.entries(headers).some(([name, value]) => carriesToken(name) || carriesToken(value))) {
45
- return { skip: 'the entry holds the seat token as a literal, which would ride a config' };
46
- }
47
- if (resolvedUrl.includes('${')) return { skip: 'its url holds a placeholder this host cannot resolve' };
48
- // A header fetch would reject quotes its value in the error, and that value
49
- // holds the token once the bridge fills it, so such an entry is not wired.
50
- if (Object.entries(headers).some(([name, value]) => !HEADER_TEXT.test(name) || !HEADER_TEXT.test(value))) {
51
- return { skip: 'a header holds a control or non-Latin-1 character' };
52
- }
53
- // The url the guard judged (isOurGrantBroker) is normalised: resolved against
54
- // the instance, with doubled slashes collapsed. The bridge fetches that same
55
- // url, on the instance's own origin, or nothing.
56
- let url;
57
- try {
58
- const instance = new URL(instanceUrl);
59
- const parsed = new URL(resolvedUrl, instance);
60
- if (parsed.origin !== instance.origin) return { skip: 'its url is not on this instance' };
61
- parsed.pathname = parsed.pathname.replace(/\/{2,}/g, '/');
62
- url = parsed.href;
63
- } catch {
64
- return { skip: 'its url does not resolve against this instance' };
65
- }
66
- return {
67
- command: process.execPath,
68
- args: [BRIDGE_PATH],
69
- env: {
70
- [BRIDGE_URL_VAR]: url,
71
- [BRIDGE_HEADERS_VAR]: JSON.stringify(headers),
72
- [CREDENTIAL_FILE_VAR]: credentialFile,
73
- },
74
- readPath: BRIDGE_PATH,
75
- };
76
- };