iit-mcp-autotask 0.11.0 → 0.11.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.
- package/README.md +7 -1
- package/dist/config/constants.d.ts +24 -0
- package/dist/config/constants.js +26 -0
- package/dist/config/credentials.d.ts +36 -0
- package/dist/config/credentials.js +79 -0
- package/dist/config/error.d.ts +5 -0
- package/dist/config/error.js +6 -0
- package/dist/config/load.d.ts +12 -0
- package/dist/config/load.js +70 -0
- package/dist/config/parse.d.ts +34 -0
- package/dist/config/parse.js +61 -0
- package/dist/config/schema.d.ts +125 -0
- package/dist/config/schema.js +274 -0
- package/dist/config.d.ts +9 -148
- package/dist/config.js +6 -494
- package/dist/main.js +7 -0
- package/dist/moves.d.ts +5 -0
- package/dist/moves.js +84 -0
- package/dist/tools/context/client.d.ts +23 -0
- package/dist/tools/context/client.js +96 -0
- package/dist/tools/context/derive.d.ts +9 -0
- package/dist/tools/context/derive.js +36 -0
- package/dist/tools/context/ops.d.ts +29 -0
- package/dist/tools/context/ops.js +70 -0
- package/dist/tools/context/pool.d.ts +5 -0
- package/dist/tools/context/pool.js +9 -0
- package/dist/tools/context/scope.d.ts +24 -0
- package/dist/tools/context/scope.js +180 -0
- package/dist/tools/context/types.d.ts +150 -0
- package/dist/tools/context/types.js +2 -0
- package/dist/tools/context.d.ts +6 -199
- package/dist/tools/context.js +5 -393
- package/dist/tools/results/envelope.d.ts +54 -0
- package/dist/tools/results/envelope.js +44 -0
- package/dist/tools/results/errors.d.ts +165 -0
- package/dist/tools/results/errors.js +402 -0
- package/dist/tools/results/untrusted.d.ts +124 -0
- package/dist/tools/results/untrusted.js +238 -0
- package/dist/tools/results.d.ts +19 -185
- package/dist/tools/results.js +3 -675
- package/dist/tools/schemas.d.ts +1 -1
- package/dist/tools/server.d.ts +2 -2
- package/dist/tools/server.js +1 -1
- package/dist/tools/write/apply-phases.d.ts +95 -0
- package/dist/tools/write/apply-phases.js +431 -0
- package/dist/tools/write/apply.d.ts +4 -0
- package/dist/tools/write/apply.js +41 -0
- package/dist/tools/write/common.d.ts +23 -0
- package/dist/tools/write/common.js +33 -0
- package/dist/tools/write/facts.d.ts +56 -0
- package/dist/tools/write/facts.js +98 -0
- package/dist/tools/write/outcome.d.ts +27 -0
- package/dist/tools/write/outcome.js +87 -0
- package/dist/tools/write/parse.d.ts +14 -0
- package/dist/tools/write/parse.js +69 -0
- package/dist/tools/write/payload.d.ts +44 -0
- package/dist/tools/write/payload.js +118 -0
- package/dist/tools/write/preview.d.ts +3 -0
- package/dist/tools/write/preview.js +224 -0
- package/dist/tools/write/snapshot.d.ts +55 -0
- package/dist/tools/write/snapshot.js +237 -0
- package/dist/tools/write.d.ts +28 -33
- package/dist/tools/write.js +5 -1154
- package/dist/write/store/audit-chain.d.ts +5 -0
- package/dist/write/store/audit-chain.js +23 -0
- package/dist/write/store/audit-log.d.ts +13 -0
- package/dist/write/store/audit-log.js +55 -0
- package/dist/write/store/constants.d.ts +34 -0
- package/dist/write/store/constants.js +35 -0
- package/dist/write/store/files.d.ts +44 -0
- package/dist/write/store/files.js +157 -0
- package/dist/write/store/heartbeat.d.ts +9 -0
- package/dist/write/store/heartbeat.js +65 -0
- package/dist/write/store/ledger.d.ts +18 -0
- package/dist/write/store/ledger.js +153 -0
- package/dist/write/store/lifecycle.d.ts +22 -0
- package/dist/write/store/lifecycle.js +87 -0
- package/dist/write/store/lock.d.ts +97 -0
- package/dist/write/store/lock.js +464 -0
- package/dist/write/store/op-lock.d.ts +13 -0
- package/dist/write/store/op-lock.js +175 -0
- package/dist/write/store/plan-store.d.ts +10 -0
- package/dist/write/store/plan-store.js +102 -0
- package/dist/write/store/plans.d.ts +5 -0
- package/dist/write/store/plans.js +19 -0
- package/dist/write/store/types.d.ts +273 -0
- package/dist/write/store/types.js +2 -0
- package/dist/write/store/unresolved.d.ts +9 -0
- package/dist/write/store/unresolved.js +45 -0
- package/dist/write/store.d.ts +6 -267
- package/dist/write/store.js +43 -1131
- package/package.json +1 -1
- package/server.json +1 -1
package/README.md
CHANGED
|
@@ -155,7 +155,13 @@ What the policy can and cannot do:
|
|
|
155
155
|
original (the vendor ignores an update of `ticketID`): the new record gets a new id, create date and
|
|
156
156
|
creator, and notifications may fire. A move into another company or contract always needs a signed
|
|
157
157
|
approval grant; an unfinished earlier move of the same source blocks another until its ledger entry ages
|
|
158
|
-
out (14 days)
|
|
158
|
+
out (14 days) or a person clears it: in a terminal, as the server's OS user, with `AUTOTASK_STATE_DIR` set,
|
|
159
|
+
`autotask-mcp moves list` shows the blocked sources and `autotask-mcp moves resolve <resource>:<id>` (after you
|
|
160
|
+
checked both tickets in Autotask; you type the source id, and the copy's id when the ledger names one) closes the
|
|
161
|
+
ledger lines as `MOVE_RESOLVED` and audits it (`move_resolved`). It refuses within 10 minutes of the move being sent
|
|
162
|
+
(its engine may still be running) and, while the engine may still run, when the sending server is still registered or alive. A plan previewed
|
|
163
|
+
before the resolve cannot apply after it. It is not an
|
|
164
|
+
MCP tool; like the approvals CLI it is a guardrail, not a boundary against an agent with a same-user shell. Billed, attached, approved, closed-target and older-than-14-days sources are refused.
|
|
159
165
|
- Hard rules no policy overrides: `authenticate.*` is never served; operations the SDK flags
|
|
160
166
|
`requiresApproval`, and non-read sensitive ones (webhooks, `resources.transferOwnership`), always
|
|
161
167
|
need approval; destructive and account-wide operations need it, and `allowUnattended` cannot waive
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** The closed value sets of the configuration: transports, profiles, toolsets, cache presets and the removed variables (issue #91). */
|
|
2
|
+
/** `stdio` = local path (Claude Code / Claude Desktop / VS Code); `http` = remote (gateway). */
|
|
3
|
+
export declare const TRANSPORTS: readonly ["stdio", "http"];
|
|
4
|
+
export type Transport = (typeof TRANSPORTS)[number];
|
|
5
|
+
/**
|
|
6
|
+
* The deployment profile (0.6.0). `readonly` (the default) serves reads only and holds no state;
|
|
7
|
+
* `operator` adds the preview/apply write path (`autotask_write`, `autotask_apply`), which needs a
|
|
8
|
+
* policy file, a state dir and a run scope (see `src/write/runtime.ts`).
|
|
9
|
+
*/
|
|
10
|
+
export declare const PROFILES: readonly ["readonly", "operator"];
|
|
11
|
+
export type Profile = (typeof PROFILES)[number];
|
|
12
|
+
/** The optional toolsets `AUTOTASK_TOOLSETS` selects; `core` is always registered. */
|
|
13
|
+
export declare const TOOLSETS: readonly ["service", "records", "finance"];
|
|
14
|
+
export type Toolset = (typeof TOOLSETS)[number];
|
|
15
|
+
/**
|
|
16
|
+
* The 0.5.x variables 0.6.0 removed, with the replacement each boot refusal names. Boot fails when
|
|
17
|
+
* any is set: a deployment that still sets one expects a behaviour this build no longer has.
|
|
18
|
+
*/
|
|
19
|
+
export declare const REMOVED_VARIABLES: Readonly<Record<string, string>>;
|
|
20
|
+
/** The per-tenant read-cache preset every client this server builds gets. */
|
|
21
|
+
export declare const CACHE_PRESETS: readonly ["on", "off"];
|
|
22
|
+
export type CachePreset = (typeof CACHE_PRESETS)[number];
|
|
23
|
+
/** The vendor's hard cap on requests per hour per integration (the local limiter must not exceed it). */
|
|
24
|
+
export declare const VENDOR_HOURLY_CAP = 10000;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** The closed value sets of the configuration: transports, profiles, toolsets, cache presets and the removed variables (issue #91). */
|
|
2
|
+
/** `stdio` = local path (Claude Code / Claude Desktop / VS Code); `http` = remote (gateway). */
|
|
3
|
+
export const TRANSPORTS = ['stdio', 'http'];
|
|
4
|
+
/**
|
|
5
|
+
* The deployment profile (0.6.0). `readonly` (the default) serves reads only and holds no state;
|
|
6
|
+
* `operator` adds the preview/apply write path (`autotask_write`, `autotask_apply`), which needs a
|
|
7
|
+
* policy file, a state dir and a run scope (see `src/write/runtime.ts`).
|
|
8
|
+
*/
|
|
9
|
+
export const PROFILES = ['readonly', 'operator'];
|
|
10
|
+
/** The optional toolsets `AUTOTASK_TOOLSETS` selects; `core` is always registered. */
|
|
11
|
+
export const TOOLSETS = ['service', 'records', 'finance'];
|
|
12
|
+
/**
|
|
13
|
+
* The 0.5.x variables 0.6.0 removed, with the replacement each boot refusal names. Boot fails when
|
|
14
|
+
* any is set: a deployment that still sets one expects a behaviour this build no longer has.
|
|
15
|
+
*/
|
|
16
|
+
export const REMOVED_VARIABLES = {
|
|
17
|
+
AUTOTASK_READ_ONLY: 'AUTOTASK_PROFILE=readonly (the default) or AUTOTASK_PROFILE=operator',
|
|
18
|
+
AUTOTASK_WRITE_POLICY: 'AUTOTASK_PROFILE=readonly (the default) or AUTOTASK_PROFILE=operator',
|
|
19
|
+
AUTOTASK_WRITE_ALLOW: 'AUTOTASK_PROFILE=operator (writes are governed by the operator write path)',
|
|
20
|
+
AUTOTASK_SECRET_READS: 'nothing: authenticate.* is always refused (credential-minting)',
|
|
21
|
+
};
|
|
22
|
+
/** The per-tenant read-cache preset every client this server builds gets. */
|
|
23
|
+
export const CACHE_PRESETS = ['on', 'off'];
|
|
24
|
+
/** The vendor's hard cap on requests per hour per integration (the local limiter must not exceed it). */
|
|
25
|
+
export const VENDOR_HOURLY_CAP = 10_000;
|
|
26
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Config } from './schema.js';
|
|
2
|
+
/** The recognised AUTOTASK_* names: the config schema's AUTOTASK_ keys + the removed 0.5.x set (derived, never hand-listed). */
|
|
3
|
+
export declare function recognisedAutotaskVars(): string[];
|
|
4
|
+
/** The set-but-unrecognised AUTOTASK_* env keys (stale 0.4.x-era vars the server never reads). */
|
|
5
|
+
export declare function unrecognisedAutotaskVars(env: Record<string, string | undefined>): string[];
|
|
6
|
+
/**
|
|
7
|
+
* Load the three server-held credential values through the SDK's canonical loader
|
|
8
|
+
* (`loadAutotaskCredentialsFromEnv`, node-autotask 0.8.0), so this process has exactly ONE parser
|
|
9
|
+
* for them: the shell-equivalent one that strips a surrounding quote pair, drops a CRLF's `\r` and
|
|
10
|
+
* trims edge whitespace — the same rules `set -a; . ./.env; set +a` applies. The deprecated aliases
|
|
11
|
+
* (`AUTOTASK_API_CODE`, `AUTOTASK_API_INTEGRATION_CODE`, `AUTOTASK_USER`) are read as fall-backs.
|
|
12
|
+
*
|
|
13
|
+
* Returns only the value-free facts: which names were seen, their shapes, and the SDK's own typed
|
|
14
|
+
* failure when the loader refused one (the SDK's `INVALID_CREDENTIAL_SHAPE` — surfaced as this
|
|
15
|
+
* server's typed `CREDENTIAL_MALFORMED` refusal, never re-implemented).
|
|
16
|
+
*/
|
|
17
|
+
export interface ServerCredentialIntake {
|
|
18
|
+
/** The SDK's own verdict: the shape problem it named, or undefined for a loadable set. */
|
|
19
|
+
readonly sdkProblem?: {
|
|
20
|
+
readonly code: string;
|
|
21
|
+
readonly message: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export declare function loadServerCredentials(env: Record<string, string | undefined>): ServerCredentialIntake;
|
|
25
|
+
/**
|
|
26
|
+
* The server-held credential defaults as a set (or `undefined` when any member is unset — the
|
|
27
|
+
* tuple is all-or-nothing on the config surface; see the module docblock for why).
|
|
28
|
+
*/
|
|
29
|
+
export declare function serverCredentialDefaults(config: Config): {
|
|
30
|
+
apiIntegrationCode: string;
|
|
31
|
+
userName: string;
|
|
32
|
+
secret: string;
|
|
33
|
+
impersonationResourceId?: string;
|
|
34
|
+
} | undefined;
|
|
35
|
+
/** True when a full server-held credential tuple is present (the discouraged `http` default). */
|
|
36
|
+
export declare function hasServerCredentialDefaults(config: Config): boolean;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Server credential intake and defaults: recognised variables, the credential load, the stdio defaults (issue #91). */
|
|
2
|
+
import { loadAutotaskCredentialsFromEnv } from 'node-autotask';
|
|
3
|
+
import { REMOVED_VARIABLES } from './constants.js';
|
|
4
|
+
import { CONFIG_FIELDS } from './schema.js';
|
|
5
|
+
/** The recognised AUTOTASK_* names: the config schema's AUTOTASK_ keys + the removed 0.5.x set (derived, never hand-listed). */
|
|
6
|
+
export function recognisedAutotaskVars() {
|
|
7
|
+
const schemaKeys = Object.keys(CONFIG_FIELDS.shape).filter((k) => k.startsWith('AUTOTASK_'));
|
|
8
|
+
return [...new Set([...schemaKeys, ...Object.keys(REMOVED_VARIABLES)])].sort();
|
|
9
|
+
}
|
|
10
|
+
/** The set-but-unrecognised AUTOTASK_* env keys (stale 0.4.x-era vars the server never reads). */
|
|
11
|
+
export function unrecognisedAutotaskVars(env) {
|
|
12
|
+
const recognised = new Set(recognisedAutotaskVars());
|
|
13
|
+
return Object.keys(env).filter((k) => k.startsWith('AUTOTASK_') && !recognised.has(k)).sort();
|
|
14
|
+
}
|
|
15
|
+
export function loadServerCredentials(env) {
|
|
16
|
+
// Only the PRODUCTION spellings are consulted: `loadAutotaskCredentialsFromEnv` also reads the
|
|
17
|
+
// deprecated aliases (`AUTOTASK_API_CODE` / `AUTOTASK_API_INTEGRATION_CODE`, `AUTOTASK_USER`), so
|
|
18
|
+
// it is called on a projection that carries exactly the variables this server documents. Without
|
|
19
|
+
// that, an http deployment with no server-held tuple at all would be told the SDK's loader
|
|
20
|
+
// "refused" a credential that this server never reads.
|
|
21
|
+
const canonical = {
|
|
22
|
+
AUTOTASK_TRACKING_IDENTIFIER: env.AUTOTASK_API_INTEGRATION_CODE,
|
|
23
|
+
AUTOTASK_USERNAME: env.AUTOTASK_USERNAME,
|
|
24
|
+
AUTOTASK_SECRET: env.AUTOTASK_SECRET,
|
|
25
|
+
AUTOTASK_IMPERSONATION_ID: env.AUTOTASK_IMPERSONATION_RESOURCE_ID,
|
|
26
|
+
};
|
|
27
|
+
const present = Object.values(canonical).filter((v) => v !== undefined && v.trim() !== '').length;
|
|
28
|
+
if (present === 0)
|
|
29
|
+
return {};
|
|
30
|
+
// A PARTIAL server-held tuple is the documented all-or-nothing case (`serverCredentialDefaults`
|
|
31
|
+
// returns undefined and the tuple is simply not served) — not a load refusal. The SDK's loader
|
|
32
|
+
// exists to load a COMPLETE credential set, so it is asked only when all three are present; a
|
|
33
|
+
// partial one is judged by the shape check above, field by field.
|
|
34
|
+
if (canonical.AUTOTASK_TRACKING_IDENTIFIER === undefined ||
|
|
35
|
+
canonical.AUTOTASK_TRACKING_IDENTIFIER.trim() === '' ||
|
|
36
|
+
canonical.AUTOTASK_USERNAME === undefined ||
|
|
37
|
+
canonical.AUTOTASK_USERNAME.trim() === '' ||
|
|
38
|
+
canonical.AUTOTASK_SECRET === undefined ||
|
|
39
|
+
canonical.AUTOTASK_SECRET.trim() === '') {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
loadAutotaskCredentialsFromEnv(canonical);
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const e = err;
|
|
48
|
+
return {
|
|
49
|
+
sdkProblem: {
|
|
50
|
+
code: typeof e.code === 'string' ? e.code : 'CONFIG_ERROR',
|
|
51
|
+
message: typeof e.message === 'string' ? e.message : 'the credential values could not be loaded',
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The server-held credential defaults as a set (or `undefined` when any member is unset — the
|
|
58
|
+
* tuple is all-or-nothing on the config surface; see the module docblock for why).
|
|
59
|
+
*/
|
|
60
|
+
export function serverCredentialDefaults(config) {
|
|
61
|
+
if (config.AUTOTASK_API_INTEGRATION_CODE === undefined ||
|
|
62
|
+
config.AUTOTASK_USERNAME === undefined ||
|
|
63
|
+
config.AUTOTASK_SECRET === undefined) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
apiIntegrationCode: config.AUTOTASK_API_INTEGRATION_CODE,
|
|
68
|
+
userName: config.AUTOTASK_USERNAME,
|
|
69
|
+
secret: config.AUTOTASK_SECRET,
|
|
70
|
+
...(config.AUTOTASK_IMPERSONATION_RESOURCE_ID !== undefined
|
|
71
|
+
? { impersonationResourceId: config.AUTOTASK_IMPERSONATION_RESOURCE_ID }
|
|
72
|
+
: {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** True when a full server-held credential tuple is present (the discouraged `http` default). */
|
|
76
|
+
export function hasServerCredentialDefaults(config) {
|
|
77
|
+
return serverCredentialDefaults(config) !== undefined;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** The configuration error every boot refusal throws (issue #91). */
|
|
2
|
+
/** Thrown only by {@link loadConfig}. Never carries a credential value — only variable names. */
|
|
3
|
+
export class ConfigError extends Error {
|
|
4
|
+
name = 'ConfigError';
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Config } from './schema.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse and validate configuration from an environment-shaped record.
|
|
4
|
+
*
|
|
5
|
+
* Takes the environment as an argument (main.ts merges the CLI's operational overrides into it)
|
|
6
|
+
* rather than reading `process.env` directly, so the failure paths are testable without mutating
|
|
7
|
+
* global state.
|
|
8
|
+
*
|
|
9
|
+
* @throws {ConfigError} when any variable is missing or malformed. The message lists every
|
|
10
|
+
* problem at once — a boot loop that surfaces one missing variable per restart wastes real minutes.
|
|
11
|
+
*/
|
|
12
|
+
export declare function loadConfig(env?: Record<string, string | undefined>): Config;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** `loadConfig`: the environment in, a validated Config out (issue #91). */
|
|
2
|
+
import { MALFORMED_CREDENTIAL_REFUSAL, detectCredentialShape, malformedCredentialBootMessage } from '../credential.js';
|
|
3
|
+
import { REMOVED_VARIABLES } from './constants.js';
|
|
4
|
+
import { CONFIG_FIELDS, schema } from './schema.js';
|
|
5
|
+
import { unrecognisedAutotaskVars, loadServerCredentials } from './credentials.js';
|
|
6
|
+
import { ConfigError } from './error.js';
|
|
7
|
+
/**
|
|
8
|
+
* Parse and validate configuration from an environment-shaped record.
|
|
9
|
+
*
|
|
10
|
+
* Takes the environment as an argument (main.ts merges the CLI's operational overrides into it)
|
|
11
|
+
* rather than reading `process.env` directly, so the failure paths are testable without mutating
|
|
12
|
+
* global state.
|
|
13
|
+
*
|
|
14
|
+
* @throws {ConfigError} when any variable is missing or malformed. The message lists every
|
|
15
|
+
* problem at once — a boot loop that surfaces one missing variable per restart wastes real minutes.
|
|
16
|
+
*/
|
|
17
|
+
export function loadConfig(env = process.env) {
|
|
18
|
+
const removed = Object.entries(REMOVED_VARIABLES)
|
|
19
|
+
.filter(([name]) => env[name] !== undefined)
|
|
20
|
+
.map(([name, use]) => ` ${name}: removed in 0.6.0, use ${use}`);
|
|
21
|
+
const parsed = schema.safeParse(env);
|
|
22
|
+
// The pre-dispatch credential shape gate (issue #4a). Runs BEFORE the schema verdict is thrown,
|
|
23
|
+
// so a malformed load is reported alongside (or instead of) the structural problems rather than
|
|
24
|
+
// being masked by them, and it names the anomaly + the load recipe in one message. Nothing here
|
|
25
|
+
// dials: the check is pure string inspection.
|
|
26
|
+
const shapeIssues = [];
|
|
27
|
+
for (const name of ['AUTOTASK_API_INTEGRATION_CODE', 'AUTOTASK_USERNAME', 'AUTOTASK_SECRET', 'AUTOTASK_IMPERSONATION_RESOURCE_ID']) {
|
|
28
|
+
const raw = env[name];
|
|
29
|
+
if (raw === undefined || raw.trim() === '')
|
|
30
|
+
continue;
|
|
31
|
+
const anomalies = detectCredentialShape(raw);
|
|
32
|
+
if (anomalies.length > 0)
|
|
33
|
+
shapeIssues.push(` ${name}: ${malformedCredentialBootMessage(name, anomalies, raw.length)}`);
|
|
34
|
+
}
|
|
35
|
+
const intake = loadServerCredentials(env);
|
|
36
|
+
if (intake.sdkProblem !== undefined) {
|
|
37
|
+
// The SDK's canonical loader refused a value our own check did not name (or named differently).
|
|
38
|
+
// Only the SDK's machine CODE is referenced: its MESSAGE embeds a value summary
|
|
39
|
+
// (`credentialValueSummary` -> a length and a two-character prefix, chunk-PBBFMRI5 `shapeError`),
|
|
40
|
+
// so quoting it would put characters of a secret into the boot log and the doctor-CLI JSON. The
|
|
41
|
+
// text served is this server's own, value-free: the loader's verdict and the load recipe.
|
|
42
|
+
shapeIssues.push(` ${intake.sdkProblem.code}: ${MALFORMED_CREDENTIAL_REFUSAL}`);
|
|
43
|
+
}
|
|
44
|
+
if (parsed.success && shapeIssues.length === 0 && removed.length === 0)
|
|
45
|
+
return parsed.data;
|
|
46
|
+
const problems = [
|
|
47
|
+
...removed,
|
|
48
|
+
...(parsed.success ? [] : parsed.error.issues.map((i) => ` ${i.path.join('.') || '(config)'}: ${i.message}`)),
|
|
49
|
+
];
|
|
50
|
+
const all = [...problems, ...shapeIssues];
|
|
51
|
+
// #73: the stale-variable diagnostics (additive, throw path only — a valid config still boots;
|
|
52
|
+
// the section is never served on the success path).
|
|
53
|
+
const unrecognised = unrecognisedAutotaskVars(env);
|
|
54
|
+
const section = [
|
|
55
|
+
'AUTOTASK_* env vars:',
|
|
56
|
+
` recognised and set: ${Object.keys(CONFIG_FIELDS.shape).filter((k) => k.startsWith('AUTOTASK_') && env[k] !== undefined).sort().join(', ') || 'none'} — the vars this server actually reads (removed names above are not read)`,
|
|
57
|
+
...(unrecognised.length > 0
|
|
58
|
+
? [
|
|
59
|
+
` unrecognised (ignored by this server): ${unrecognised.join(', ')}`,
|
|
60
|
+
'Unrecognised AUTOTASK_* vars are ignored by this server; if they belong to a newer (or older) server version, check the version this deployment runs, then upgrade or remove them.',
|
|
61
|
+
'Known legacy renames: AUTOTASK_API_CODE -> set AUTOTASK_API_INTEGRATION_CODE instead (the pinned SDK\'s canonical name).',
|
|
62
|
+
]
|
|
63
|
+
: []),
|
|
64
|
+
];
|
|
65
|
+
const tail = `\n\n${section.join('\n')}`;
|
|
66
|
+
throw new ConfigError(shapeIssues.length > 0
|
|
67
|
+
? `Invalid configuration (credential intake refused a value):\n${all.join('\n')}${tail}`
|
|
68
|
+
: `Invalid configuration:\n${all.join('\n')}${tail}`);
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=load.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Small parsers and predicates the configuration schema uses: hosts, toolsets, the run scope, approval mode (issue #91). */
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* A run scope: the companies one operator principal may write in this run. `{all: true}` means the
|
|
5
|
+
* policy's own scope; company ids are intersected with it, so a run scope can only narrow.
|
|
6
|
+
*/
|
|
7
|
+
export declare const RUN_SCOPE: z.ZodUnion<readonly [z.ZodObject<{
|
|
8
|
+
all: z.ZodLiteral<true>;
|
|
9
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
10
|
+
companyIds: z.ZodArray<z.ZodNumber>;
|
|
11
|
+
}, z.core.$strict>]>;
|
|
12
|
+
/** True for an address only this host can reach (the HTTP bind decides what boot requires). */
|
|
13
|
+
export declare function isLoopbackHost(host: string): boolean;
|
|
14
|
+
/** A comma list of hostnames (lowercased, de-duplicated), or undefined when unset or blank. */
|
|
15
|
+
export declare function parseHostList(raw: string | undefined): string[] | undefined;
|
|
16
|
+
/** `AUTOTASK_TOOLSETS` parsed once: a comma list of toolset names, `all` = every toolset. */
|
|
17
|
+
export declare function parseToolsets(raw: string | undefined): string[];
|
|
18
|
+
/**
|
|
19
|
+
* `AUTOTASK_ALLOWED_BASE_HOSTS` parsed once, here.
|
|
20
|
+
*
|
|
21
|
+
* Unset or blank stays `undefined` (no list); otherwise a lowercased, de-duplicated list.
|
|
22
|
+
* Entry validation (shape + "could match a real hostname") happens in the schema refinement.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseBaseHosts(raw: string | undefined): string[] | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* A plausible hostname label (RFC 952/1123): 1-63 lowercase alphanumerics + hyphens, no leading or
|
|
27
|
+
* trailing hyphen. The `*` wildcard is not a real label: it is legal only as the first label of a
|
|
28
|
+
* `*.` suffix entry, which the caller strips before delegating here.
|
|
29
|
+
*/
|
|
30
|
+
export declare const HOST_LABEL: RegExp;
|
|
31
|
+
/** Whether an entry could match a real hostname, or only a typo (a silent dead entry). */
|
|
32
|
+
export declare function isPlausibleHost(entry: string): boolean;
|
|
33
|
+
/** Blank-to-undefined preprocessor: `VAR=` (a copied `.env.example` line) means "unset". */
|
|
34
|
+
export declare const blankToUndefined: z.ZodPreprocess<z.ZodString, unknown>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Small parsers and predicates the configuration schema uses: hosts, toolsets, the run scope, approval mode (issue #91). */
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
import { TOOLSETS } from './constants.js';
|
|
4
|
+
/**
|
|
5
|
+
* A run scope: the companies one operator principal may write in this run. `{all: true}` means the
|
|
6
|
+
* policy's own scope; company ids are intersected with it, so a run scope can only narrow.
|
|
7
|
+
*/
|
|
8
|
+
export const RUN_SCOPE = z.union([
|
|
9
|
+
z.object({ all: z.literal(true) }).strict(),
|
|
10
|
+
z.object({ companyIds: z.array(z.number().int().positive()).min(1).max(1000) }).strict(),
|
|
11
|
+
]);
|
|
12
|
+
/** True for an address only this host can reach (the HTTP bind decides what boot requires). */
|
|
13
|
+
export function isLoopbackHost(host) {
|
|
14
|
+
const h = host.toLowerCase().replace(/^\[|\]$/g, '');
|
|
15
|
+
return h === 'localhost' || h === '::1' || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h);
|
|
16
|
+
}
|
|
17
|
+
/** A comma list of hostnames (lowercased, de-duplicated), or undefined when unset or blank. */
|
|
18
|
+
export function parseHostList(raw) {
|
|
19
|
+
if (raw === undefined || raw.trim() === '')
|
|
20
|
+
return undefined;
|
|
21
|
+
return [...new Set(raw.split(',').map((h) => h.trim().toLowerCase()).filter((h) => h.length > 0))];
|
|
22
|
+
}
|
|
23
|
+
/** `AUTOTASK_TOOLSETS` parsed once: a comma list of toolset names, `all` = every toolset. */
|
|
24
|
+
export function parseToolsets(raw) {
|
|
25
|
+
const names = (raw ?? '')
|
|
26
|
+
.split(',')
|
|
27
|
+
.map((name) => name.trim().toLowerCase())
|
|
28
|
+
.filter((name) => name.length > 0);
|
|
29
|
+
return names.includes('all') ? [...TOOLSETS] : [...new Set(names)];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `AUTOTASK_ALLOWED_BASE_HOSTS` parsed once, here.
|
|
33
|
+
*
|
|
34
|
+
* Unset or blank stays `undefined` (no list); otherwise a lowercased, de-duplicated list.
|
|
35
|
+
* Entry validation (shape + "could match a real hostname") happens in the schema refinement.
|
|
36
|
+
*/
|
|
37
|
+
export function parseBaseHosts(raw) {
|
|
38
|
+
if (raw === undefined || raw.trim() === '')
|
|
39
|
+
return undefined;
|
|
40
|
+
const parsed = raw
|
|
41
|
+
.split(',')
|
|
42
|
+
.map((host) => host.trim().toLowerCase())
|
|
43
|
+
.filter((host) => host.length > 0);
|
|
44
|
+
return [...new Set(parsed)];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A plausible hostname label (RFC 952/1123): 1-63 lowercase alphanumerics + hyphens, no leading or
|
|
48
|
+
* trailing hyphen. The `*` wildcard is not a real label: it is legal only as the first label of a
|
|
49
|
+
* `*.` suffix entry, which the caller strips before delegating here.
|
|
50
|
+
*/
|
|
51
|
+
export const HOST_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
52
|
+
/** Whether an entry could match a real hostname, or only a typo (a silent dead entry). */
|
|
53
|
+
export function isPlausibleHost(entry) {
|
|
54
|
+
const body = entry.startsWith('*.') ? entry.slice(2) : entry;
|
|
55
|
+
if (body === '')
|
|
56
|
+
return false;
|
|
57
|
+
return body.split('.').every((label) => HOST_LABEL.test(label));
|
|
58
|
+
}
|
|
59
|
+
/** Blank-to-undefined preprocessor: `VAR=` (a copied `.env.example` line) means "unset". */
|
|
60
|
+
export const blankToUndefined = z.preprocess((raw) => (typeof raw === 'string' && raw.trim() === '' ? undefined : raw), z.string());
|
|
61
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/** The configuration schema: the field table and the validated Config type (issue #91). */
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
/** The approval mode each profile runs under (readonly writes nothing, so there is nothing to approve). */
|
|
4
|
+
export declare function approvalMode(config: Pick<Config, 'AUTOTASK_PROFILE'> & Partial<Pick<Config, 'AUTOTASK_POLICY_FILE'>>): 'off' | 'policy' | 'external';
|
|
5
|
+
export declare const CONFIG_FIELDS: z.ZodObject<{
|
|
6
|
+
MCP_TRANSPORT: z.ZodDefault<z.ZodEnum<{
|
|
7
|
+
stdio: "stdio";
|
|
8
|
+
http: "http";
|
|
9
|
+
}>>;
|
|
10
|
+
MCP_HOST: z.ZodPreprocess<z.ZodDefault<z.ZodString>, unknown>;
|
|
11
|
+
AUTOTASK_HTTP_ALLOWED_HOSTS: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string[] | undefined, string | undefined>>;
|
|
12
|
+
AUTOTASK_HTTP_ALLOWED_ORIGINS: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string[] | undefined, string | undefined>>;
|
|
13
|
+
AUTOTASK_HTTP_AUTH: z.ZodOptional<z.ZodEnum<{
|
|
14
|
+
tokens: "tokens";
|
|
15
|
+
gateway: "gateway";
|
|
16
|
+
}>>;
|
|
17
|
+
AUTOTASK_HTTP_TOKENS_FILE: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
18
|
+
AUTOTASK_HTTP_RATE_PER_MIN: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
19
|
+
AUTOTASK_BASE_URL: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
|
|
20
|
+
AUTOTASK_API_INTEGRATION_CODE: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
21
|
+
AUTOTASK_USERNAME: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
22
|
+
AUTOTASK_SECRET: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
23
|
+
AUTOTASK_IMPERSONATION_RESOURCE_ID: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
24
|
+
AUTOTASK_ALLOWED_BASE_HOSTS: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string[] | undefined, string | undefined>>;
|
|
25
|
+
AUTOTASK_ALLOW_ANY_BASE_HOST: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
|
|
26
|
+
true: "true";
|
|
27
|
+
false: "false";
|
|
28
|
+
}>>, z.ZodTransform<boolean, "true" | "false">>;
|
|
29
|
+
AUTOTASK_PROFILE: z.ZodDefault<z.ZodEnum<{
|
|
30
|
+
readonly: "readonly";
|
|
31
|
+
operator: "operator";
|
|
32
|
+
}>>;
|
|
33
|
+
AUTOTASK_POLICY_FILE: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
34
|
+
AUTOTASK_STATE_DIR: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
35
|
+
AUTOTASK_KILL_FILE: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
36
|
+
AUTOTASK_PRINCIPAL: z.ZodPreprocess<z.ZodDefault<z.ZodString>, unknown>;
|
|
37
|
+
AUTOTASK_RUN_SCOPE: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<{
|
|
38
|
+
all: true;
|
|
39
|
+
} | {
|
|
40
|
+
companyIds: number[];
|
|
41
|
+
} | undefined, string | undefined>>;
|
|
42
|
+
AUTOTASK_TOOLSETS: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string[], string | undefined>>;
|
|
43
|
+
AUTOTASK_CACHE_PRESET: z.ZodDefault<z.ZodEnum<{
|
|
44
|
+
on: "on";
|
|
45
|
+
off: "off";
|
|
46
|
+
}>>;
|
|
47
|
+
AUTOTASK_COMPOSITE_NAMES: z.ZodDefault<z.ZodEnum<{
|
|
48
|
+
on: "on";
|
|
49
|
+
off: "off";
|
|
50
|
+
}>>;
|
|
51
|
+
AUTOTASK_FIELD_CACHE_TTL_MS: z.ZodPreprocess<z.ZodOptional<z.ZodNumber>, unknown>;
|
|
52
|
+
AUTOTASK_OUTPUT_DIR: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
53
|
+
AUTOTASK_RATE_LIMIT_PER_HOUR: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
54
|
+
AUTOTASK_MAX_CONCURRENT: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
55
|
+
AUTOTASK_TIMEOUT_MS: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
56
|
+
AUTOTASK_MAX_RETRIES: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
57
|
+
PORT: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
58
|
+
LOG_LEVEL: z.ZodDefault<z.ZodEnum<{
|
|
59
|
+
error: "error";
|
|
60
|
+
debug: "debug";
|
|
61
|
+
info: "info";
|
|
62
|
+
warn: "warn";
|
|
63
|
+
}>>;
|
|
64
|
+
}, z.core.$strip>;
|
|
65
|
+
export declare const schema: z.ZodObject<{
|
|
66
|
+
MCP_TRANSPORT: z.ZodDefault<z.ZodEnum<{
|
|
67
|
+
stdio: "stdio";
|
|
68
|
+
http: "http";
|
|
69
|
+
}>>;
|
|
70
|
+
MCP_HOST: z.ZodPreprocess<z.ZodDefault<z.ZodString>, unknown>;
|
|
71
|
+
AUTOTASK_HTTP_ALLOWED_HOSTS: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string[] | undefined, string | undefined>>;
|
|
72
|
+
AUTOTASK_HTTP_ALLOWED_ORIGINS: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string[] | undefined, string | undefined>>;
|
|
73
|
+
AUTOTASK_HTTP_AUTH: z.ZodOptional<z.ZodEnum<{
|
|
74
|
+
tokens: "tokens";
|
|
75
|
+
gateway: "gateway";
|
|
76
|
+
}>>;
|
|
77
|
+
AUTOTASK_HTTP_TOKENS_FILE: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
78
|
+
AUTOTASK_HTTP_RATE_PER_MIN: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
79
|
+
AUTOTASK_BASE_URL: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
|
|
80
|
+
AUTOTASK_API_INTEGRATION_CODE: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
81
|
+
AUTOTASK_USERNAME: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
82
|
+
AUTOTASK_SECRET: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
83
|
+
AUTOTASK_IMPERSONATION_RESOURCE_ID: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
84
|
+
AUTOTASK_ALLOWED_BASE_HOSTS: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string[] | undefined, string | undefined>>;
|
|
85
|
+
AUTOTASK_ALLOW_ANY_BASE_HOST: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
|
|
86
|
+
true: "true";
|
|
87
|
+
false: "false";
|
|
88
|
+
}>>, z.ZodTransform<boolean, "true" | "false">>;
|
|
89
|
+
AUTOTASK_PROFILE: z.ZodDefault<z.ZodEnum<{
|
|
90
|
+
readonly: "readonly";
|
|
91
|
+
operator: "operator";
|
|
92
|
+
}>>;
|
|
93
|
+
AUTOTASK_POLICY_FILE: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
94
|
+
AUTOTASK_STATE_DIR: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
95
|
+
AUTOTASK_KILL_FILE: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
96
|
+
AUTOTASK_PRINCIPAL: z.ZodPreprocess<z.ZodDefault<z.ZodString>, unknown>;
|
|
97
|
+
AUTOTASK_RUN_SCOPE: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<{
|
|
98
|
+
all: true;
|
|
99
|
+
} | {
|
|
100
|
+
companyIds: number[];
|
|
101
|
+
} | undefined, string | undefined>>;
|
|
102
|
+
AUTOTASK_TOOLSETS: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<string[], string | undefined>>;
|
|
103
|
+
AUTOTASK_CACHE_PRESET: z.ZodDefault<z.ZodEnum<{
|
|
104
|
+
on: "on";
|
|
105
|
+
off: "off";
|
|
106
|
+
}>>;
|
|
107
|
+
AUTOTASK_COMPOSITE_NAMES: z.ZodDefault<z.ZodEnum<{
|
|
108
|
+
on: "on";
|
|
109
|
+
off: "off";
|
|
110
|
+
}>>;
|
|
111
|
+
AUTOTASK_FIELD_CACHE_TTL_MS: z.ZodPreprocess<z.ZodOptional<z.ZodNumber>, unknown>;
|
|
112
|
+
AUTOTASK_OUTPUT_DIR: z.ZodOptional<z.ZodPreprocess<z.ZodString, unknown>>;
|
|
113
|
+
AUTOTASK_RATE_LIMIT_PER_HOUR: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
114
|
+
AUTOTASK_MAX_CONCURRENT: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
115
|
+
AUTOTASK_TIMEOUT_MS: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
116
|
+
AUTOTASK_MAX_RETRIES: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
117
|
+
PORT: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
118
|
+
LOG_LEVEL: z.ZodDefault<z.ZodEnum<{
|
|
119
|
+
error: "error";
|
|
120
|
+
debug: "debug";
|
|
121
|
+
info: "info";
|
|
122
|
+
warn: "warn";
|
|
123
|
+
}>>;
|
|
124
|
+
}, z.core.$strip>;
|
|
125
|
+
export type Config = z.infer<typeof schema>;
|