Skip to content
Closed
4 changes: 2 additions & 2 deletions packages/cli/src/commands/adopt.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import os from 'os';
import { E } from '@endo/far';
import { withEndoAgent } from '../context.js';
import { parsePetNamePath } from '../pet-name.js';
import { parseNumber } from '../number-parse.js';
import { parseBigint } from '../number-parse.js';

export const adoptCommand = async ({
messageNumberText,
Expand All @@ -13,7 +13,7 @@ export const adoptCommand = async ({
}) =>
withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
await E(agent).adopt(
parseNumber(messageNumberText),
parseBigint(messageNumberText),
edgeName,
parsePetNamePath(name),
);
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/approve-eval.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os from 'os';
import { E } from '@endo/far';
import { withEndoAgent } from '../context.js';
import { parseNumber } from '../number-parse.js';
import { parseBigint } from '../number-parse.js';

export const approveEvalCommand = async ({
messageNumberText,
Expand All @@ -11,7 +11,7 @@ export const approveEvalCommand = async ({
}) =>
withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
await E(agent).approveEvaluation(
parseNumber(messageNumberText),
parseBigint(messageNumberText),
workerName,
);
});
35 changes: 35 additions & 0 deletions packages/cli/src/commands/define.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* global process */
import os from 'os';
import { E } from '@endo/far';
import { withEndoAgent } from '../context.js';

/**
* Parse --slot arguments into a slots record.
* Each slot is "codeName:label" e.g. "counter:A counter to increment"
*
* @param {string[]} slotArgs
* @returns {Record<string, { label: string }>}
*/
const parseSlots = slotArgs => {
/** @type {Record<string, { label: string }>} */
const slots = Object.create(null);
for (const arg of slotArgs) {
const colonIndex = arg.indexOf(':');
if (colonIndex === -1) {
throw new Error(
`Invalid slot format ${JSON.stringify(arg)}, expected "codeName:label"`,
);
}
const codeName = arg.slice(0, colonIndex);
const label = arg.slice(colonIndex + 1);
slots[codeName] = { label };
}
return slots;
};

export const defineCommand = async ({ source, slotArgs, agentNames }) =>
withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
const slots = parseSlots(slotArgs);
const result = await E(agent).define(source, slots);
console.log(result);
});
5 changes: 2 additions & 3 deletions packages/cli/src/commands/dismiss.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
import os from 'os';
import { E } from '@endo/far';
import { withEndoAgent } from '../context.js';
import { parseBigint } from '../number-parse.js';

export const dismissCommand = async ({ messageNumberText, agentNames }) =>
withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
// TODO less bad number parsing.
const messageNumber = Number(messageNumberText);
await E(agent).dismiss(messageNumber);
await E(agent).dismiss(parseBigint(messageNumberText));
});
46 changes: 46 additions & 0 deletions packages/cli/src/commands/endow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* global process */
import os from 'os';
import { E } from '@endo/far';
import { withEndoAgent } from '../context.js';
import { parseBigint } from '../number-parse.js';

/**
* Parse --bind arguments into a bindings record.
* Each binding is "codeName:petName" e.g. "counter:my-counter"
*
* @param {string[]} bindArgs
* @returns {Record<string, string>}
*/
const parseBindings = bindArgs => {
/** @type {Record<string, string>} */
const bindings = Object.create(null);
for (const arg of bindArgs) {
const colonIndex = arg.indexOf(':');
if (colonIndex === -1) {
throw new Error(
`Invalid binding format ${JSON.stringify(arg)}, expected "codeName:petName"`,
);
}
const codeName = arg.slice(0, colonIndex);
const petName = arg.slice(colonIndex + 1);
bindings[codeName] = petName;
}
return bindings;
};

export const endowCommand = async ({
messageNumberText,
bindArgs,
workerName,
resultName,
agentNames,
}) =>
withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
const bindings = parseBindings(bindArgs);
await E(agent).endow(
parseBigint(messageNumberText),
bindings,
workerName,
resultName,
);
});
47 changes: 47 additions & 0 deletions packages/cli/src/commands/form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* global process */
import os from 'os';
import { E } from '@endo/far';
import { withEndoAgent } from '../context.js';
import { parseOptionalPetNamePath } from '../pet-name.js';

/**
* Parse --field arguments into a fields record.
* Each field is "fieldName:label" e.g. "name:Your name"
*
* @param {string[]} fieldArgs
* @returns {Record<string, { label: string }>}
*/
const parseFields = fieldArgs => {
/** @type {Record<string, { label: string }>} */
const fields = Object.create(null);
for (const arg of fieldArgs) {
const colonIndex = arg.indexOf(':');
if (colonIndex === -1) {
throw new Error(
`Invalid field format ${JSON.stringify(arg)}, expected "fieldName:label"`,
);
}
const fieldName = arg.slice(0, colonIndex);
const label = arg.slice(colonIndex + 1);
fields[fieldName] = { label };
}
return fields;
};

export const formCommand = async ({
toName,
description,
fieldArgs,
resultName,
agentNames,
}) =>
withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
const fields = parseFields(fieldArgs);
const result = await E(agent).form(
toName,
description,
fields,
parseOptionalPetNamePath(resultName),
);
console.log(result);
});
14 changes: 14 additions & 0 deletions packages/cli/src/commands/inbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export const inbox = async ({ follow, agentNames }) =>
verb = 'requested';
} else if (type === 'package') {
verb = 'sent';
} else if (type === 'definition') {
verb = 'proposed code';
} else if (type === 'form-request') {
verb = 'requested form';
} else {
verb = 'sent an unrecognizable message';
}
Expand Down Expand Up @@ -66,6 +70,16 @@ export const inbox = async ({ follow, agentNames }) =>
edgeNames,
)} at ${JSON.stringify(date)}`,
);
} else if (message.type === 'definition') {
const slotNames = Object.keys(message.slots).join(', ');
console.log(
`${number}. ${provenance}(slots: ${slotNames}) at ${JSON.stringify(date)}`,
);
} else if (message.type === 'form-request') {
const fieldNames = Object.keys(message.fields).join(', ');
console.log(
`${number}. ${provenance}${JSON.stringify(message.description)} (fields: ${fieldNames}) at ${JSON.stringify(date)}`,
);
} else {
console.log(`${number}. ${provenance}, consider upgrading.`);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/reject.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
import os from 'os';
import { E } from '@endo/far';
import { withEndoAgent } from '../context.js';
import { parseNumber } from '../number-parse.js';
import { parseBigint } from '../number-parse.js';

export const rejectCommand = async ({
requestNumberText,
message,
agentNames,
}) =>
withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
await E(agent).reject(parseNumber(requestNumberText), message);
await E(agent).reject(parseBigint(requestNumberText), message);
});
4 changes: 2 additions & 2 deletions packages/cli/src/commands/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
import os from 'os';
import { E } from '@endo/far';
import { withEndoAgent } from '../context.js';
import { parseNumber } from '../number-parse.js';
import { parseBigint } from '../number-parse.js';

export const resolveCommand = async ({
requestNumberText,
resolutionName,
agentNames,
}) =>
withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
await E(agent).resolve(parseNumber(requestNumberText), resolutionName);
await E(agent).resolve(parseBigint(requestNumberText), resolutionName);
});
39 changes: 39 additions & 0 deletions packages/cli/src/commands/respond-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* global process */
import os from 'os';
import { E } from '@endo/far';
import { withEndoAgent } from '../context.js';
import { parseBigint } from '../number-parse.js';

/**
* Parse --value arguments into a values record.
* Each value is "fieldName:value" e.g. "name:Alice"
*
* @param {string[]} valueArgs
* @returns {Record<string, string>}
*/
const parseValues = valueArgs => {
/** @type {Record<string, string>} */
const values = Object.create(null);
for (const arg of valueArgs) {
const colonIndex = arg.indexOf(':');
if (colonIndex === -1) {
throw new Error(
`Invalid value format ${JSON.stringify(arg)}, expected "fieldName:value"`,
);
}
const fieldName = arg.slice(0, colonIndex);
const value = arg.slice(colonIndex + 1);
values[fieldName] = value;
}
return values;
};

export const respondFormCommand = async ({
messageNumberText,
valueArgs,
agentNames,
}) =>
withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
const values = parseValues(valueArgs);
await E(agent).respondForm(parseBigint(messageNumberText), values);
});
102 changes: 102 additions & 0 deletions packages/cli/src/endo.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,108 @@ export const main = async rawArgs => {
});
});

program
.command('define <source>')
.description(
'propose code with named capability slots for the host to endow',
)
.option(...commonOptions.as)
.option(
'-s,--slot <slot>',
'Slot definition as codeName:label (repeatable)',
(val, acc) => {
acc.push(val);
return acc;
},
[],
)
.action(async (source, cmd) => {
const { as: agentNames, slot: slotArgs } = cmd.opts();
const { defineCommand } = await import('./commands/define.js');
return defineCommand({ source, slotArgs, agentNames });
});

program
.command('endow <message-number>')
.description('bind capabilities to a definition and evaluate')
.option(...commonOptions.as)
.option(
'-b,--bind <binding>',
'Binding as codeName:petName (repeatable)',
(val, acc) => {
acc.push(val);
return acc;
},
[],
)
.option('-w,--worker <name>', 'Worker to use for evaluation')
.option(...commonOptions.name)
.action(async (messageNumberText, cmd) => {
const {
as: agentNames,
bind: bindArgs,
worker: workerName,
name: resultName,
} = cmd.opts();
const { endowCommand } = await import('./commands/endow.js');
return endowCommand({
messageNumberText,
bindArgs,
workerName,
resultName,
agentNames,
});
});

program
.command('form <recipient> <description>')
.description('send a structured form request')
.option(...commonOptions.as)
.option(...commonOptions.name)
.option(
'-f,--field <field>',
'Field definition as fieldName:label (repeatable)',
(val, acc) => {
acc.push(val);
return acc;
},
[],
)
.action(async (toName, description, cmd) => {
const { as: agentNames, field: fieldArgs, name: resultName } = cmd.opts();
const { formCommand } = await import('./commands/form.js');
return formCommand({
toName,
description,
fieldArgs,
resultName,
agentNames,
});
});

program
.command('respond-form <message-number>')
.description('respond to a form request with values')
.option(...commonOptions.as)
.option(
'-v,--value <value>',
'Value as fieldName:value (repeatable)',
(val, acc) => {
acc.push(val);
return acc;
},
[],
)
.action(async (messageNumberText, cmd) => {
const { as: agentNames, value: valueArgs } = cmd.opts();
const { respondFormCommand } = await import('./commands/respond-form.js');
return respondFormCommand({
messageNumberText,
valueArgs,
agentNames,
});
});

program
.command('send <agent> <message-with-embedded-references>')
.description('send a message with @named-values @for-you:from-me')
Expand Down
Loading
Loading