@trydreamstate/cli 1.0.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.
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildPackParser = buildPackParser;
4
+ exports.packScaffold = packScaffold;
5
+ exports.runPackInit = runPackInit;
6
+ exports.runPackCommand = runPackCommand;
7
+ exports.renderPackUsage = renderPackUsage;
8
+ const node_util_1 = require("node:util");
9
+ // CanonicalApiCapabilityId comes from ../index on purpose. Do not reach into the
10
+ // shared types package's built dist for it: those built id unions have no source on
11
+ // main and no generator, so they still carry pre-cutover spellings, and
12
+ // audit:canonical-cutover:clean treats importing them as a release blocker.
13
+ const index_1 = require("../index");
14
+ const node_fs_1 = require("node:fs");
15
+ const MODES = ['dry_run', 'live', 'smoke_test'];
16
+ function parseObject(raw, flag) {
17
+ try {
18
+ const value = JSON.parse(raw);
19
+ if (!value || typeof value !== 'object' || Array.isArray(value))
20
+ throw new Error('must contain a JSON object');
21
+ return value;
22
+ }
23
+ catch (error) {
24
+ throw new index_1.CliUsageError(`${flag} ${error instanceof Error ? error.message : 'is invalid JSON'}`);
25
+ }
26
+ }
27
+ function parseJson(raw, flag) {
28
+ try {
29
+ return JSON.parse(raw);
30
+ }
31
+ catch {
32
+ throw new index_1.CliUsageError(`${flag} must contain valid JSON`);
33
+ }
34
+ }
35
+ function parseMode(value) {
36
+ if (value === undefined)
37
+ return undefined;
38
+ if (MODES.includes(value))
39
+ return value;
40
+ throw new index_1.CliUsageError(`--mode must be one of: ${MODES.join(', ')}`);
41
+ }
42
+ function buildPackParser(pack, kind, specs, usage) {
43
+ return (argv) => {
44
+ const spec = specs.find((candidate) => candidate.verb === argv[0]);
45
+ if (!spec)
46
+ throw new index_1.CliUsageError(`Unknown ${pack} verb: ${argv[0] ?? '(missing)'}\n${usage}`);
47
+ const options = {
48
+ mode: { type: 'string' }, 'dry-run': { type: 'boolean' }, live: { type: 'boolean' }, 'agent-session-id': { type: 'string' },
49
+ 'idempotency-key': { type: 'string' }, 'max-credits': { type: 'string' }, params: { type: 'string' },
50
+ 'json-output': { type: 'boolean' }, open: { type: 'boolean' }, help: { type: 'boolean', short: 'h' },
51
+ };
52
+ // A verb that takes a JSON document on a flag also takes it from a file, so a
53
+ // whole authored document can be linted or imported straight off disk instead of
54
+ // being pasted through the shell. The competitor authors this way and it is the
55
+ // only thing their file workflow had that ours did not.
56
+ // A verb that declares its own `file` flag owns the name, for example init, whose
57
+ // --file is where the scaffold is written rather than a document to read.
58
+ const declaresOwnFile = Object.keys(spec.flags ?? {}).includes('file');
59
+ const jsonFlag = declaresOwnFile
60
+ ? undefined
61
+ : Object.entries(spec.flags ?? {}).find(([, definition]) => definition.document === true);
62
+ if (!declaresOwnFile)
63
+ options.file = { type: 'string' };
64
+ for (const [flag, definition] of Object.entries(spec.flags ?? {}))
65
+ options[flag] = { type: definition.type };
66
+ const { values, positionals } = (0, node_util_1.parseArgs)({ args: argv.slice(1), options, allowPositionals: true, strict: true });
67
+ const mode = values.mode;
68
+ const dryRun = values['dry-run'];
69
+ const live = values.live;
70
+ const agentSessionId = values['agent-session-id'];
71
+ const idempotencyKey = values['idempotency-key'];
72
+ const rawParams = values.params;
73
+ const rawMaxCredits = values['max-credits'];
74
+ if (values.help)
75
+ throw new index_1.CliUsageError(usage);
76
+ const positional = spec.positional ?? [];
77
+ // A document supplied inline or from a file is authored, not saved, so there is no
78
+ // id to name yet. lint and its kin accept a hypothetical document, which is the
79
+ // whole point of authoring in a file before anything exists in the workspace.
80
+ const documentSupplied = values.file !== undefined
81
+ || (jsonFlag !== undefined && values[jsonFlag[0]] !== undefined);
82
+ const requiredPositionals = positional.filter((definition) => definition.optional !== true).length;
83
+ const positionalsAcceptable = positionals.length <= positional.length
84
+ && (positionals.length >= requiredPositionals || (documentSupplied && positionals.length === 0));
85
+ if (!positionalsAcceptable)
86
+ throw new index_1.CliUsageError(`Usage: ${usage}`);
87
+ if ((mode && (dryRun || live)) || (dryRun && live)) {
88
+ throw new index_1.CliUsageError('Use exactly one of --mode, --dry-run, or --live.');
89
+ }
90
+ const input = rawParams ? parseObject(rawParams, '--params') : {};
91
+ positional.forEach((definition, index) => {
92
+ const value = positionals[index];
93
+ if (value !== undefined)
94
+ input[definition.into] = value;
95
+ });
96
+ for (const [flag, definition] of Object.entries(spec.flags ?? {})) {
97
+ const value = values[flag];
98
+ if (value === undefined)
99
+ continue;
100
+ if (definition.json) {
101
+ input[definition.into] = parseJson(String(value), `--${flag}`);
102
+ }
103
+ else if (definition.number) {
104
+ const parsedNumber = Number(String(value));
105
+ if (!Number.isFinite(parsedNumber))
106
+ throw new index_1.CliUsageError(`--${flag} must be a number`);
107
+ input[definition.into] = parsedNumber;
108
+ }
109
+ else {
110
+ input[definition.into] = value;
111
+ }
112
+ }
113
+ const filePath = declaresOwnFile ? undefined : values.file;
114
+ if (filePath !== undefined) {
115
+ if (jsonFlag && values[jsonFlag[0]] !== undefined) {
116
+ throw new index_1.CliUsageError(`Use either --${jsonFlag[0]} or --file, not both.`);
117
+ }
118
+ let contents;
119
+ try {
120
+ contents = (0, node_fs_1.readFileSync)(filePath, 'utf8');
121
+ }
122
+ catch (error) {
123
+ throw new index_1.CliUsageError(`--file could not be read: ${error instanceof Error ? error.message : String(error)}`);
124
+ }
125
+ const parsed = parseJson(contents, `--file ${filePath}`);
126
+ if (jsonFlag) {
127
+ input[jsonFlag[1].into] = parsed;
128
+ }
129
+ else if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
130
+ // A verb with no single document flag spreads the file's fields across its
131
+ // params, which is what `dreamstate <pack> init` writes and what `create`
132
+ // needs: name and steps are separate params there, not one document. Without
133
+ // this, the scaffold composed with no verb at all.
134
+ Object.assign(input, parsed);
135
+ }
136
+ else {
137
+ throw new index_1.CliUsageError(`--file for ${pack} ${spec.verb} must contain a JSON object of params.`);
138
+ }
139
+ }
140
+ // kind is the discriminator of the SHARED authoring family only. The
141
+ // family-specific capabilities have strict schemas that do not declare it,
142
+ // so injecting it everywhere made every one of those verbs fail validation
143
+ // with an unrecognized key before it reached the server's real work.
144
+ if (spec.capabilityId.startsWith('workflow.automations.'))
145
+ input.kind = kind;
146
+ const explicitMode = dryRun ? 'dry_run' : live ? 'live' : parseMode(mode);
147
+ const maxCredits = rawMaxCredits === undefined ? undefined : Number(rawMaxCredits);
148
+ if (maxCredits !== undefined && (!Number.isFinite(maxCredits) || maxCredits < 0))
149
+ throw new index_1.CliUsageError('--max-credits must be a non-negative number');
150
+ return {
151
+ command: 'pack', pack, verb: spec.verb, capabilityId: spec.capabilityId, input,
152
+ ...(explicitMode ? { mode: explicitMode } : spec.mutating ? { mode: 'dry_run' } : {}),
153
+ ...(agentSessionId ? { agentSessionId } : {}),
154
+ ...(idempotencyKey ? { idempotencyKey } : {}),
155
+ ...(maxCredits === undefined ? {} : { maxCredits }), ...(values['json-output'] ? { jsonOutput: true } : {}),
156
+ ...(values.open ? { open: true } : {}),
157
+ };
158
+ };
159
+ }
160
+ /**
161
+ * A starter document written to disk, so authoring can begin from a file the way the
162
+ * rest of this pack now accepts one. It writes nothing to the workspace and calls no
163
+ * API: the point is to hand back something `lint --file` will accept and refuse
164
+ * usefully, rather than to guess a valid sequence on the caller's behalf.
165
+ */
166
+ function packScaffold(kind, name) {
167
+ // The step fields are FLAT and the step carries an id. An earlier scaffold nested
168
+ // channel/action under `fields` and omitted the id, and the document it wrote was
169
+ // refused by our own lint with `node_contract_unresolved: Node unknown has no
170
+ // canonical node-contract mapping`, because canonicalSequenceStepContractNode reads
171
+ // step.kind and step.action off the step itself. A scaffold whose output the next
172
+ // command in its own printed next_steps rejects is worse than no scaffold.
173
+ return kind === 'sequence'
174
+ ? {
175
+ kind: 'sequence',
176
+ name,
177
+ steps: [
178
+ {
179
+ id: 'step_1',
180
+ kind: 'linkedin',
181
+ channel: 'linkedin',
182
+ // send_dm, not send_connection_request. compose refuses the latter on purpose:
183
+ // a send_dm carrying note_template sends the invitation and then body_template
184
+ // once it is accepted, so one step covers both. A scaffold naming the refused
185
+ // step lints clean and then fails at create, which is the same trap as before.
186
+ action: 'send_dm',
187
+ note_template: 'Hi {{first_name}}, would love to connect.',
188
+ body_template: 'Thanks for connecting, {{first_name}}.',
189
+ },
190
+ ],
191
+ }
192
+ : { kind: 'workflow', name, nodes: [], edges: [] };
193
+ }
194
+ function runPackInit(kind, command, stdout) {
195
+ if (command.command !== 'pack')
196
+ throw new index_1.CliUsageError('Pack command required.');
197
+ const input = command.input;
198
+ const name = typeof input.name === 'string' && input.name.trim() ? input.name.trim() : `My ${kind}`;
199
+ const target = typeof input.file === 'string' && input.file.trim()
200
+ ? input.file.trim()
201
+ : `./${kind}.json`;
202
+ if ((0, node_fs_1.existsSync)(target)) {
203
+ throw new index_1.CliUsageError(`${target} already exists. Pass --file <path> to write somewhere else.`);
204
+ }
205
+ const document = packScaffold(kind, name);
206
+ (0, node_fs_1.writeFileSync)(target, `${JSON.stringify(document, null, 2)}\n`, 'utf8');
207
+ stdout.write(`${JSON.stringify({
208
+ ok: true,
209
+ data: {
210
+ file: target,
211
+ kind,
212
+ next_steps: [
213
+ `dreamstate ${kind}s lint --file ${target} --json-output`,
214
+ `dreamstate ${kind}s import --file ${target} --json-output`,
215
+ ],
216
+ },
217
+ }, null, command.jsonOutput ? 0 : 2)}\n`);
218
+ }
219
+ async function runPackCommand(client, command, stdout) {
220
+ if (command.command !== 'pack')
221
+ throw new index_1.CliUsageError('Pack command required.');
222
+ // Every run needs a tool_turn_token from tools_get, exactly as the generic
223
+ // tools run path does. Without it the server refuses capability_not_fetched,
224
+ // which made every pack verb unusable even though its capability id was right.
225
+ const { toolTurnToken } = require('../index');
226
+ const turnToken = toolTurnToken(await client.getTool(command.capabilityId));
227
+ const result = await client.runCapability(command.capabilityId, {
228
+ params: command.input, tool_turn_token: turnToken, ...(command.mode ? { mode: command.mode } : {}),
229
+ ...(command.agentSessionId ? { agent_session_id: command.agentSessionId } : {}),
230
+ ...(command.maxCredits === undefined ? {} : { max_credits: command.maxCredits }),
231
+ }, command.idempotencyKey);
232
+ stdout.write(`${JSON.stringify(result, null, command.jsonOutput ? 0 : 2)}\n`);
233
+ if (command.open) {
234
+ const root = result && typeof result === 'object' ? result : {};
235
+ const webUrl = root.meta?.web_url ?? root.data?.web_url;
236
+ if (typeof webUrl !== 'string')
237
+ throw new index_1.CliUsageError('The API response did not include a web_url.');
238
+ (0, index_1.openWebUrl)(webUrl);
239
+ }
240
+ }
241
+ function renderPackUsage(pack, specs) {
242
+ return `Usage: dreamstate ${pack} <verb> [args] [flags]\n\nVerbs:\n${specs.map((spec) => ` ${spec.verb.padEnd(18)} ${spec.summary}`).join('\n')}`;
243
+ }
@@ -0,0 +1,7 @@
1
+ import { runPackCommand, type PackVerbSpec } from './shared';
2
+ import type { CliCommand } from '../index';
3
+ export declare const WORKFLOW_VERBS: PackVerbSpec[];
4
+ export declare const WORKFLOWS_USAGE: string;
5
+ export declare const parseWorkflowsCommand: (argv: string[]) => CliCommand;
6
+ export declare function runWorkflowsCommand(client: Parameters<typeof runPackCommand>[0], command: CliCommand, stdout: NodeJS.WritableStream): Promise<void>;
7
+ //# sourceMappingURL=workflows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflows.d.ts","sourceRoot":"","sources":["../../src/commands/workflows.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,cAAc,EAEd,KAAK,YAAY,EAClB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAS3C,eAAO,MAAM,cAAc,EAAE,YAAY,EA8BxC,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,MAAqD,CAAC;AACpF,eAAO,MAAM,qBAAqB,gCAA4E,CAAC;AAC/G,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,CAAC,EAC5C,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,MAAM,CAAC,cAAc,GAC5B,OAAO,CAAC,IAAI,CAAC,CAMf"}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseWorkflowsCommand = exports.WORKFLOWS_USAGE = exports.WORKFLOW_VERBS = void 0;
4
+ exports.runWorkflowsCommand = runWorkflowsCommand;
5
+ const shared_1 = require("./shared");
6
+ const id = [{ name: 'id', into: 'automation_id' }];
7
+ const workflowId = [{ name: 'id', into: 'workflow_id' }];
8
+ const campaignId = [{ name: 'id', into: 'campaign_id' }];
9
+ const workflowVersion = {
10
+ 'expected-latest-draft-version-id': { type: 'string', into: 'expected_latest_draft_version_id' },
11
+ };
12
+ exports.WORKFLOW_VERBS = [
13
+ { verb: 'list', capabilityId: 'workflow.automations.list', summary: 'List workflows.', flags: { limit: { type: 'string', into: 'limit' }, cursor: { type: 'string', into: 'cursor' }, lifecycle: { type: 'string', into: 'lifecycle' }, 'include-archived': { type: 'boolean', into: 'include_archived' }, search: { type: 'string', into: 'search' } }, mutating: false },
14
+ // Local only: writes a starter document to disk and calls no API, so authoring can
15
+ // begin from a file. capabilityId names the verb this scaffold is meant to feed.
16
+ { verb: 'init', capabilityId: 'workflow.automations.lint', summary: 'Write a starter workflow document to a local file. Calls no API and changes nothing.', flags: { name: { type: 'string', into: 'name' }, file: { type: 'string', into: 'file' } }, mutating: false },
17
+ { verb: 'get', capabilityId: 'workflow.automations.get', summary: 'Get workflow.', positional: id, mutating: false },
18
+ { verb: 'create', capabilityId: 'workflow.automations.compose', summary: 'Create workflow.', flags: { name: { type: 'string', into: 'name' }, description: { type: 'string', into: 'description' }, trigger: { type: 'string', into: 'trigger', json: true }, steps: { type: 'string', into: 'steps', json: true } }, mutating: true },
19
+ { verb: 'update', capabilityId: 'workflow.automations.update', summary: 'Update workflow.', positional: id, flags: { ...workflowVersion, patch: { type: 'string', into: 'patch', json: true, document: true } }, mutating: true },
20
+ { verb: 'lint', capabilityId: 'workflow.automations.lint', summary: 'Lint workflow.', positional: id, flags: { phase: { type: 'string', into: 'phase' }, document: { type: 'string', into: 'document', json: true, document: true } }, mutating: false },
21
+ { verb: 'preview', capabilityId: 'workflow.automations.preview', summary: 'Preview workflow effects.', positional: id, mutating: false },
22
+ { verb: 'variables', capabilityId: 'workflow.automations.variables_list', summary: 'List workflow variables.', positional: id, flags: { 'node-id': { type: 'string', into: 'node_id' }, query: { type: 'string', into: 'query' }, limit: { type: 'string', into: 'limit' }, document: { type: 'string', into: 'document', json: true, document: true } }, mutating: false },
23
+ { verb: 'node-catalog', capabilityId: 'workflow.automations.node_catalog', summary: 'List workflow node types.', flags: { 'node-kind': { type: 'string', into: 'node_kind' }, query: { type: 'string', into: 'query' }, family: { type: 'string', into: 'family' }, 'include-blocked': { type: 'boolean', into: 'include_blocked' } }, mutating: false },
24
+ { verb: 'node-inspect', capabilityId: 'workflow.automations.node_inspect', summary: 'Inspect workflow node type.', flags: { 'node-kind': { type: 'string', into: 'node_kind' }, subtype: { type: 'string', into: 'subtype' }, config: { type: 'string', into: 'config', json: true }, 'automation-id': { type: 'string', into: 'automation_id' }, 'node-id': { type: 'string', into: 'node_id' } }, mutating: false },
25
+ { verb: 'node-add', capabilityId: 'workflow.automations.node_add', summary: 'Add workflow node.', positional: id, flags: { ...workflowVersion, 'after-node-id': { type: 'string', into: 'after_node_id' }, branch: { type: 'string', into: 'branch' }, node: { type: 'string', into: 'node', json: true, document: true } }, mutating: true },
26
+ { verb: 'node-patch', capabilityId: 'workflow.automations.node_patch', summary: 'Patch workflow node.', positional: id, flags: { ...workflowVersion, 'node-id': { type: 'string', into: 'node_id' }, fields: { type: 'string', into: 'fields', json: true } }, mutating: true },
27
+ { verb: 'node-remove', capabilityId: 'workflow.automations.node_remove', summary: 'Remove workflow node.', positional: id, flags: { ...workflowVersion, 'node-id': { type: 'string', into: 'node_id' } }, mutating: true },
28
+ { verb: 'node-move', capabilityId: 'workflow.automations.node_move', summary: 'Move workflow node.', positional: id, flags: { ...workflowVersion, 'node-id': { type: 'string', into: 'node_id' }, 'after-node-id': { type: 'string', into: 'after_node_id' }, branch: { type: 'string', into: 'branch' } }, mutating: true },
29
+ { verb: 'field-options', capabilityId: 'workflow.automations.field_options', summary: 'Resolve workflow node field options.', flags: { 'node-kind': { type: 'string', into: 'node_kind' }, subtype: { type: 'string', into: 'subtype' }, 'automation-id': { type: 'string', into: 'automation_id' }, 'node-id': { type: 'string', into: 'node_id' }, 'field-key': { type: 'string', into: 'field_key' }, 'field-keys': { type: 'string', into: 'field_keys', json: true }, query: { type: 'string', into: 'query' }, limit: { type: 'string', into: 'limit' }, cursor: { type: 'string', into: 'cursor' }, 'field-context': { type: 'string', into: 'field_context', json: true } }, mutating: false },
30
+ { verb: 'draft-save', capabilityId: 'workflow.workflows.draft_save', summary: 'Save workflow draft.', positional: id, flags: { graph: { type: 'string', into: 'graph', json: true, document: true }, 'expected-latest-draft-version-id': { type: 'string', into: 'expected_latest_draft_version_id' } }, mutating: true },
31
+ { verb: 'publish', capabilityId: 'workflow.workflows.draft_publish', summary: 'Publish workflow draft.', positional: id, flags: { 'expected-draft-version-id': { type: 'string', into: 'expected_draft_version_id' }, 'expected-active-version-id': { type: 'string', into: 'expected_active_version_id' } }, mutating: true },
32
+ { verb: 'activate', capabilityId: 'workflow.workflows.activate', summary: 'Activate workflow.', positional: id, flags: { 'expected-version-id': { type: 'string', into: 'expected_version_id' }, 'delivery-binding': { type: 'string', into: 'delivery_binding', json: true } }, mutating: true },
33
+ { verb: 'deactivate', capabilityId: 'workflow.workflows.deactivate', summary: 'Deactivate workflow.', positional: id, mutating: true },
34
+ { verb: 'enroll', capabilityId: 'workflow.workflows.enroll_selection', summary: 'Enroll selection in workflow.', positional: id, flags: { selection: { type: 'string', into: 'selection', json: true } }, mutating: true },
35
+ { verb: 'runs', capabilityId: 'workflow.workflows.runs_list', summary: 'List workflow runs.', positional: workflowId, flags: { workflow: { type: 'string', into: 'workflow_id' }, limit: { type: 'string', into: 'limit' } }, mutating: false },
36
+ { verb: 'run', capabilityId: 'workflow.workflows.run_trace_get', summary: 'Get workflow run trace.', positional: id, flags: { 'subject-id': { type: 'string', into: 'subject_id' } }, mutating: false },
37
+ { verb: 'retry', capabilityId: 'workflow.workflows.run_retry', summary: 'Retry failed workflow run event.', positional: id, flags: { 'subject-id': { type: 'string', into: 'subject_id' }, 'failed-event-id': { type: 'string', into: 'failed_event_id' }, 'failed-node-id': { type: 'string', into: 'failed_node_id' } }, mutating: true },
38
+ { verb: 'duplicate', capabilityId: 'workflow.automations.duplicate', summary: 'Duplicate workflow.', positional: id, flags: { ...workflowVersion, name: { type: 'string', into: 'name' } }, mutating: true },
39
+ { verb: 'archive', capabilityId: 'workflow.automations.archive', summary: 'Archive workflow.', positional: id, flags: { ...workflowVersion, reason: { type: 'string', into: 'reason' } }, mutating: true },
40
+ { verb: 'export', capabilityId: 'workflow.automations.export', summary: 'Export workflow.', positional: id, mutating: false },
41
+ { verb: 'import', capabilityId: 'workflow.automations.import', summary: 'Import workflow.', flags: { name: { type: 'string', into: 'name' }, document: { type: 'string', into: 'document', json: true, document: true } }, mutating: true },
42
+ ];
43
+ exports.WORKFLOWS_USAGE = (0, shared_1.renderPackUsage)('workflows', exports.WORKFLOW_VERBS);
44
+ exports.parseWorkflowsCommand = (0, shared_1.buildPackParser)('workflows', 'workflow', exports.WORKFLOW_VERBS, exports.WORKFLOWS_USAGE);
45
+ async function runWorkflowsCommand(client, command, stdout) {
46
+ if (command.command === 'pack' && command.verb === 'init') {
47
+ (0, shared_1.runPackInit)('workflow', command, stdout);
48
+ return;
49
+ }
50
+ await (0, shared_1.runPackCommand)(client, command, stdout);
51
+ }
@@ -0,0 +1,25 @@
1
+ export declare class MissingApiUrlError extends Error {
2
+ readonly code = "missing_api_url";
3
+ constructor(message?: string);
4
+ }
5
+ export declare class ApiBaseUrlError extends Error {
6
+ readonly code = "invalid_api_url";
7
+ constructor(message: string);
8
+ }
9
+ export declare class HostPinMismatchError extends Error {
10
+ readonly code = "host_pin_mismatch";
11
+ constructor(message: string);
12
+ }
13
+ export declare class MissingApiKeyError extends Error {
14
+ readonly code = "not_authenticated";
15
+ constructor(message?: string);
16
+ }
17
+ export declare class ApiKeyFileError extends Error {
18
+ constructor(message: string);
19
+ }
20
+ export declare function credentialsPath(homeDirectory?: string, environment?: NodeJS.ProcessEnv): string;
21
+ export declare function resolveApiBaseUrl(environment?: NodeJS.ProcessEnv): string;
22
+ export declare function resolveApiKey(environment?: NodeJS.ProcessEnv, path?: string): Promise<string>;
23
+ export declare function readApiKeyFile(path: string): Promise<string>;
24
+ export declare function storeApiKey(apiKey: string, path?: string): Promise<string>;
25
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,qBAAqB;gBAEtB,OAAO,SAA8C;CAIlE;AAED,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,qBAAqB;gBAEtB,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,uBAAuB;gBACxB,OAAO,EAAE,MAAM;CAC5B;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,uBAAuB;gBAGlC,OAAO,SAA6E;CAKvF;AAED,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAgB,eAAe,CAC7B,aAAa,SAAY,EACzB,WAAW,GAAE,MAAM,CAAC,UAAwB,GAC3C,MAAM,CAGR;AAED,wBAAgB,iBAAiB,CAAC,WAAW,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CA+BtF;AAWD,wBAAsB,aAAa,CACjC,WAAW,GAAE,MAAM,CAAC,UAAwB,EAC5C,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CAajB;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBlE;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,SAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAW3F"}
package/dist/config.js ADDED
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiKeyFileError = exports.MissingApiKeyError = exports.HostPinMismatchError = exports.ApiBaseUrlError = exports.MissingApiUrlError = void 0;
4
+ exports.credentialsPath = credentialsPath;
5
+ exports.resolveApiBaseUrl = resolveApiBaseUrl;
6
+ exports.resolveApiKey = resolveApiKey;
7
+ exports.readApiKeyFile = readApiKeyFile;
8
+ exports.storeApiKey = storeApiKey;
9
+ const promises_1 = require("node:fs/promises");
10
+ const node_os_1 = require("node:os");
11
+ const node_path_1 = require("node:path");
12
+ class MissingApiUrlError extends Error {
13
+ code = 'missing_api_url';
14
+ constructor(message = 'No API URL found. Set DREAMSTATE_API_URL.') {
15
+ super(message);
16
+ this.name = 'MissingApiUrlError';
17
+ }
18
+ }
19
+ exports.MissingApiUrlError = MissingApiUrlError;
20
+ class ApiBaseUrlError extends Error {
21
+ code = 'invalid_api_url';
22
+ constructor(message) {
23
+ super(message);
24
+ this.name = 'ApiBaseUrlError';
25
+ }
26
+ }
27
+ exports.ApiBaseUrlError = ApiBaseUrlError;
28
+ class HostPinMismatchError extends Error {
29
+ code = 'host_pin_mismatch';
30
+ constructor(message) { super(message); this.name = 'HostPinMismatchError'; }
31
+ }
32
+ exports.HostPinMismatchError = HostPinMismatchError;
33
+ class MissingApiKeyError extends Error {
34
+ code = 'not_authenticated';
35
+ constructor(message = 'No API key found. Run `dreamstate auth login` or set DREAMSTATE_API_KEY.') {
36
+ super(message);
37
+ this.name = 'MissingApiKeyError';
38
+ }
39
+ }
40
+ exports.MissingApiKeyError = MissingApiKeyError;
41
+ class ApiKeyFileError extends Error {
42
+ constructor(message) {
43
+ super(message);
44
+ this.name = 'ApiKeyFileError';
45
+ }
46
+ }
47
+ exports.ApiKeyFileError = ApiKeyFileError;
48
+ function credentialsPath(homeDirectory = (0, node_os_1.homedir)(), environment = process.env) {
49
+ const configuredDirectory = environment.DREAMSTATE_CONFIG_DIR?.trim();
50
+ return (0, node_path_1.join)(configuredDirectory || (0, node_path_1.join)(homeDirectory, '.dreamstate'), 'credentials');
51
+ }
52
+ function resolveApiBaseUrl(environment = process.env) {
53
+ const candidate = environment.DREAMSTATE_API_URL?.trim() || 'https://api.trydreamstate.com/api/v1';
54
+ let url;
55
+ try {
56
+ url = new URL(candidate);
57
+ }
58
+ catch {
59
+ throw new ApiBaseUrlError('DREAMSTATE_API_URL must be an absolute HTTPS URL, or explicit loopback HTTP URL.');
60
+ }
61
+ if (url.username || url.password) {
62
+ throw new ApiBaseUrlError('DREAMSTATE_API_URL must not contain credentials.');
63
+ }
64
+ if (url.protocol !== 'https:' && url.protocol !== 'http:') {
65
+ throw new ApiBaseUrlError('DREAMSTATE_API_URL must use HTTPS, or HTTP on explicit loopback only.');
66
+ }
67
+ if (url.protocol === 'http:' && !isExplicitLoopbackHttpOrigin(candidate, url)) {
68
+ throw new ApiBaseUrlError('DREAMSTATE_API_URL may use HTTP only for localhost, 127.0.0.1, or [::1].');
69
+ }
70
+ const pathSegments = url.pathname.split('/').filter(Boolean);
71
+ if (!pathSegments.some((segment, index) => segment === 'api' && pathSegments[index + 1] === 'v1')) {
72
+ url.pathname = `${url.pathname.replace(/\/+$/, '')}/api/v1`;
73
+ }
74
+ const resolved = url.toString().replace(/\/+$/, '');
75
+ const required = environment.DREAMSTATE_REQUIRE_API_URL?.trim();
76
+ if (required) {
77
+ let requiredUrl;
78
+ try {
79
+ requiredUrl = new URL(required);
80
+ }
81
+ catch {
82
+ throw new HostPinMismatchError('DREAMSTATE_REQUIRE_API_URL must be an absolute URL.');
83
+ }
84
+ if (url.origin !== requiredUrl.origin) {
85
+ throw new HostPinMismatchError(`API host pin mismatch: configured ${url.origin}, required ${requiredUrl.origin}.`);
86
+ }
87
+ }
88
+ return resolved;
89
+ }
90
+ function isExplicitLoopbackHttpOrigin(candidate, url) {
91
+ const authority = candidate.match(/^http:\/\/([^/?#]*)/i)?.[1];
92
+ if (!authority || authority !== url.host)
93
+ return false;
94
+ return authority === 'localhost' || authority === '127.0.0.1' || authority === '[::1]'
95
+ || /^localhost:\d+$/.test(authority)
96
+ || /^127\.0\.0\.1:\d+$/.test(authority)
97
+ || /^\[::1\]:\d+$/.test(authority);
98
+ }
99
+ async function resolveApiKey(environment = process.env, path) {
100
+ const environmentKey = environment.DREAMSTATE_API_KEY?.trim();
101
+ if (environmentKey)
102
+ return environmentKey;
103
+ const credentialFilePath = path ?? credentialsPath(undefined, environment);
104
+ try {
105
+ const storedKey = (await (0, promises_1.readFile)(credentialFilePath, 'utf8')).trim();
106
+ if (storedKey)
107
+ return storedKey;
108
+ }
109
+ catch (error) {
110
+ if (error.code !== 'ENOENT')
111
+ throw error;
112
+ }
113
+ throw new MissingApiKeyError();
114
+ }
115
+ async function readApiKeyFile(path) {
116
+ let file;
117
+ try {
118
+ file = await (0, promises_1.stat)(path);
119
+ }
120
+ catch (error) {
121
+ if (error.code === 'ENOENT') {
122
+ throw new ApiKeyFileError(`--api-key-file not found: ${path}`);
123
+ }
124
+ throw error;
125
+ }
126
+ if (!file.isFile()) {
127
+ throw new ApiKeyFileError(`--api-key-file must point to a regular file: ${path}`);
128
+ }
129
+ if (process.platform !== 'win32' && (file.mode & 0o777) !== 0o600) {
130
+ throw new ApiKeyFileError(`--api-key-file must be mode 0600: ${path}`);
131
+ }
132
+ return (await (0, promises_1.readFile)(path, 'utf8')).trim();
133
+ }
134
+ async function storeApiKey(apiKey, path = credentialsPath()) {
135
+ const normalized = apiKey.trim();
136
+ if (!normalized.startsWith('ds_live_')) {
137
+ throw new Error('Dreamstate API keys must start with ds_live_.');
138
+ }
139
+ const directory = (0, node_path_1.join)(path, '..');
140
+ await (0, promises_1.mkdir)(directory, { recursive: true, mode: 0o700 });
141
+ await (0, promises_1.writeFile)(path, `${normalized}\n`, { encoding: 'utf8', mode: 0o600 });
142
+ await (0, promises_1.chmod)(path, 0o600);
143
+ return path;
144
+ }
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ import { type ChildProcess } from 'node:child_process';
3
+ import { type SkillInstallAgent } from './skillInstaller';
4
+ export type CanonicalApiCapabilityId = string;
5
+ export declare const CLI_EXIT: {
6
+ readonly SUCCESS: 0;
7
+ readonly UNCLASSIFIED: 1;
8
+ readonly USAGE: 2;
9
+ readonly AUTH: 3;
10
+ readonly NOT_FOUND: 4;
11
+ readonly SERVER: 5;
12
+ readonly RATE_LIMITED: 6;
13
+ readonly APPROVAL: 7;
14
+ readonly TIMEOUT: 8;
15
+ readonly REFUSED: 9;
16
+ readonly TRANSPORT: 10;
17
+ };
18
+ export type CliCommand = {
19
+ command: 'help';
20
+ } | {
21
+ command: 'version';
22
+ } | {
23
+ command: 'login';
24
+ apiKeyFile?: string;
25
+ token?: string;
26
+ jsonOutput?: boolean;
27
+ } | {
28
+ command: 'logout';
29
+ jsonOutput?: boolean;
30
+ } | {
31
+ command: 'whoami';
32
+ jsonOutput?: boolean;
33
+ } | {
34
+ command: 'packs_list';
35
+ jsonOutput?: boolean;
36
+ } | {
37
+ command: 'packs_get';
38
+ packId: string;
39
+ jsonOutput?: boolean;
40
+ } | {
41
+ command: 'packs_route';
42
+ goal: string;
43
+ jsonOutput?: boolean;
44
+ } | {
45
+ command: 'skills_list';
46
+ query: Record<string, unknown>;
47
+ jsonOutput?: boolean;
48
+ } | {
49
+ command: 'skills_get';
50
+ skillId: string;
51
+ jsonOutput?: boolean;
52
+ } | {
53
+ command: 'skills_search';
54
+ query: Record<string, unknown>;
55
+ jsonOutput?: boolean;
56
+ } | {
57
+ command: 'skills_install';
58
+ skillId: string;
59
+ agents: SkillInstallAgent[];
60
+ project?: boolean;
61
+ copy?: boolean;
62
+ jsonOutput?: boolean;
63
+ } | {
64
+ command: 'skills_doctor';
65
+ agents: SkillInstallAgent[];
66
+ project?: boolean;
67
+ jsonOutput?: boolean;
68
+ } | {
69
+ command: 'commands_search';
70
+ term: string;
71
+ jsonOutput?: boolean;
72
+ } | {
73
+ command: 'tools_search';
74
+ query: Record<string, unknown>;
75
+ jsonOutput?: boolean;
76
+ } | {
77
+ command: 'tools_get';
78
+ capabilityId: string;
79
+ jsonOutput?: boolean;
80
+ } | {
81
+ command: 'tools_run';
82
+ capabilityId: string;
83
+ body: Record<string, unknown>;
84
+ turnToken?: string;
85
+ idempotencyKey?: string;
86
+ open?: boolean;
87
+ jsonOutput?: boolean;
88
+ } | {
89
+ command: 'pack';
90
+ pack: 'sequences' | 'workflows';
91
+ verb: string;
92
+ capabilityId: CanonicalApiCapabilityId;
93
+ input: Record<string, unknown>;
94
+ pathParams?: Record<string, string>;
95
+ mode?: 'dry_run' | 'live' | 'smoke_test';
96
+ agentSessionId?: string;
97
+ idempotencyKey?: string;
98
+ jsonOutput?: boolean;
99
+ open?: boolean;
100
+ maxCredits?: number;
101
+ } | {
102
+ command: 'tables_import';
103
+ tableId: string;
104
+ file: string;
105
+ format?: 'csv' | 'json' | 'jsonl' | 'xlsx';
106
+ preview?: boolean;
107
+ upsertKey?: string;
108
+ background: boolean;
109
+ jsonOutput?: boolean;
110
+ } | {
111
+ command: 'runs_list';
112
+ query: Record<string, unknown>;
113
+ jsonOutput?: boolean;
114
+ } | {
115
+ command: 'runs_get';
116
+ runId: string;
117
+ jsonOutput?: boolean;
118
+ } | {
119
+ command: 'runs_resume';
120
+ runId: string;
121
+ stateVersion: number;
122
+ reason?: string;
123
+ idempotencyKey?: string;
124
+ open?: boolean;
125
+ jsonOutput?: boolean;
126
+ } | {
127
+ command: 'runs_events';
128
+ runId: string;
129
+ jsonOutput?: boolean;
130
+ } | {
131
+ command: 'runs_cancel';
132
+ runId: string;
133
+ body: Record<string, unknown>;
134
+ idempotencyKey?: string;
135
+ jsonOutput?: boolean;
136
+ };
137
+ export interface CliRuntime {
138
+ stdin?: NodeJS.ReadableStream & {
139
+ isTTY?: boolean;
140
+ };
141
+ stdout?: NodeJS.WritableStream;
142
+ env?: NodeJS.ProcessEnv;
143
+ promptHidden?: (prompt: string) => Promise<string>;
144
+ credentialsPath?: string;
145
+ requestTimeoutMs?: number;
146
+ openWebUrl?: (url: string) => void;
147
+ stderr?: NodeJS.WritableStream;
148
+ deviceState?: string;
149
+ sleep?: (milliseconds: number) => Promise<void>;
150
+ homeDirectory?: string;
151
+ }
152
+ export declare class CliUsageError extends Error {
153
+ constructor(message: string);
154
+ }
155
+ export declare function parseCliArgs(argv: string[]): CliCommand;
156
+ export declare function toolTurnToken(value: unknown): string;
157
+ export declare function refusalEnvelope(error: unknown, exitCode: number): Record<string, unknown>;
158
+ type SpawnLike = (command: string, args: readonly string[], options: {
159
+ detached: boolean;
160
+ stdio: 'ignore';
161
+ shell: false;
162
+ }) => Pick<ChildProcess, 'unref'>;
163
+ export declare function openWebUrl(rawUrl: string, launch?: SpawnLike): void;
164
+ export declare function exitCodeForError(error: unknown): number;
165
+ export declare function runCli(argv?: string[], runtime?: CliRuntime): Promise<void>;
166
+ export {};
167
+ //# sourceMappingURL=index.d.ts.map