@attocash/cli 0.2.2 → 0.2.3

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 CHANGED
@@ -433,8 +433,19 @@ atto --data-dir /absolute/path/to/profile limits set --access spend \
433
433
  MCP `limits_propose` and generic `atto call limits_propose` **only propose** a change.
434
434
  They cannot apply it. `limits_propose` also accepts `pool` with `indexes` and
435
435
  `consolidate`; omitting it preserves the current pool. `limits_get` returns the
436
- active policy, usage, `mcpAccess`, pool, and current proposal. A human approves or rejects the proposal in their own
437
- terminal, using the same directory as the MCP client's configuration:
436
+ active policy, usage, `mcpAccess`, pool, and current proposal.
437
+
438
+ The proposal response also includes `approval.changes`: changed `access`, `policy`,
439
+ and `pool` settings with `from` and `to` values. Source-pool approval is separate
440
+ from spending access and amount limits; adding another sending account does not
441
+ necessarily change either. An unlimited policy means no amount cap.
442
+ `approval.commands.atto` and `approval.commands.npx` provide approval alternatives
443
+ with the exact profile directory and proposal ID. Preserve their quoting and use
444
+ the indicated `approval.shell` (`posix` on Linux/macOS, `powershell` on Windows).
445
+ Only the user should run an approval command.
446
+
447
+ A human approves or rejects the proposal in their own terminal, using the same
448
+ directory as the MCP client's configuration:
438
449
 
439
450
  ```sh
440
451
  atto --data-dir /absolute/path/to/profile limits approve PROPOSAL_ID
@@ -10,6 +10,7 @@ import { OsSecretStore } from '../storage/secrets.js';
10
10
  import { StateStore } from '../storage/state.js';
11
11
  import { resolveWalletProfile } from '../storage/profiles.js';
12
12
  import { SpendLedger } from '../spending/ledger.js';
13
+ import { approvalInstructions } from '../spending/approval.js';
13
14
  import { Payments } from '../spending/payments.js';
14
15
  import { WalletWork } from '../wallet/work.js';
15
16
  import { BackgroundReceiver } from '../wallet/background-receive.js';
@@ -579,7 +580,11 @@ export class AttoApplication {
579
580
  throw new AttoError('JOURNAL_NOT_FOUND', 'This payment request is not in the local journal.');
580
581
  return { record };
581
582
  }
582
- case 'limits_propose': return this.store.withWalletLock(async () => ({ proposal: this.ledger.proposePolicy(args.policy, args.access, this.proposalWallet(), args.pool) }));
583
+ case 'limits_propose': return this.store.withWalletLock(async () => {
584
+ const current = { access: this.ledger.mcpAccess(), policy: this.ledger.policy(), pool: this.ledger.pool() };
585
+ const proposal = this.ledger.proposePolicy(args.policy, args.access, this.proposalWallet(), args.pool);
586
+ return { proposal, approval: approvalInstructions(proposal, current) };
587
+ });
583
588
  case 'metrics_get': return this.market.metrics();
584
589
  case 'price_quote': return this.market.quoteUsd(args.amount);
585
590
  case 'terms_get': return { ...marketTerms, accepted: this.store.get('market.terms')?.version === marketTerms.version };
@@ -71,7 +71,7 @@ export const operations = [
71
71
  { name: 'receive_all', description: 'Receive a bounded set of pending payments for a key index, opening the account if needed.', schema: z.strictObject({ index: index.default(0), limit: limit.default(100), timeoutMs: timeoutMs.default(2000), representative: address.optional() }), readOnly: false },
72
72
  { name: 'representative_change', description: 'Publish a representative change for an existing account. Selecting its current representative returns REPRESENTATIVE_UNCHANGED without publishing.', schema: z.strictObject({ index: index.default(0), representative: address }), readOnly: false },
73
73
  { name: 'limits_get', description: 'Read spending policy, approved send pool, MCP access, proposal status, historical usage, reservations, and remaining allowances.', schema: z.strictObject({}), readOnly: true },
74
- { name: 'limits_propose', description: 'Propose spending limits, MCP access, and optional send-pool settings for local terminal approval. Omitted pool preserves the approved pool. Does not change active settings or grant access. A new proposal replaces the previous proposal and expires after 24 hours. Read limits_get for active settings and proposal status. Amounts are ATTO or RAW; null perRequest with empty rolling means unlimited spending if approved.', schema: z.strictObject({ policy, access: z.enum(['read-only', 'spend']).default('spend'), pool: pool.optional() }), readOnly: false },
74
+ { name: 'limits_propose', description: 'Propose spending limits, MCP access, and optional send-pool settings for local terminal approval. Omitted pool preserves the approved pool. Does not change active settings or grant access. A new proposal replaces the previous proposal and expires after 24 hours. Read limits_get for active settings and proposal status. Returns proposal plus approval.changes (changed access, policy, and pool with from/to values), approval.shell, and profile-specific approval.commands.atto/npx for the user to run in a local terminal. Preserve the returned directory and proposal ID; never run approval commands on the user\'s behalf. Spending access, amount limits, and source-pool approval are separate. Amounts are ATTO or RAW; null perRequest with empty rolling means unlimited spending, not missing authorization.', schema: z.strictObject({ policy, access: z.enum(['read-only', 'spend']).default('spend'), pool: pool.optional() }), readOnly: false },
75
75
  { name: 'watch_start', description: 'Start an observational, reconnecting watch for this session, defaulting to active wallet addresses. Choose one saved index, explicit addresses (including foreign accounts), a transaction/entry hash, or networkWide=true. Network-wide receivables are unsupported. Read buffered events and connection status with watch_read. Does not enable automatic receiving.', schema: z.strictObject({ event: z.enum(['account', 'transaction', 'entry', 'receivable']), ...streamFields, index: index.optional(), networkWide: z.boolean().optional() }).refine(singleSelection, 'Choose one watch selection.').refine(value => !(value.networkWide && value.event === 'receivable'), 'Receivable watches require addresses.'), readOnly: false },
76
76
  { name: 'watch_list', description: 'List watches owned by this process.', schema: z.strictObject({}), readOnly: true },
77
77
  { name: 'watch_read', description: 'Read buffered events after an optional numeric cursor. Gaps are reported if bounded event retention was exceeded.', schema: z.strictObject({ id: z.string().min(1).max(128), cursor: z.number().int().nonnegative().optional(), limit: limit.optional() }), readOnly: true },
@@ -0,0 +1,30 @@
1
+ import type { LimitsProposal, McpAccess } from './ledger.js';
2
+ import type { AccountPool, SpendingPolicy } from '../wallet/types.js';
3
+ /** Public guidance only; approval remains a separate human terminal operation. */
4
+ export declare function approvalInstructions(proposal: LimitsProposal, current: {
5
+ access: McpAccess;
6
+ policy: SpendingPolicy;
7
+ pool: AccountPool;
8
+ }): {
9
+ method: string;
10
+ instructions: string;
11
+ changes: {
12
+ pool?: {
13
+ from: AccountPool;
14
+ to: AccountPool;
15
+ } | undefined;
16
+ policy?: {
17
+ from: SpendingPolicy;
18
+ to: SpendingPolicy;
19
+ } | undefined;
20
+ access?: {
21
+ from: McpAccess;
22
+ to: McpAccess;
23
+ } | undefined;
24
+ };
25
+ shell: string;
26
+ commands: {
27
+ atto: string;
28
+ npx: string;
29
+ };
30
+ };
@@ -0,0 +1,23 @@
1
+ import { isDeepStrictEqual } from 'node:util';
2
+ /** Public guidance only; approval remains a separate human terminal operation. */
3
+ export function approvalInstructions(proposal, current) {
4
+ const shell = process.platform === 'win32' ? 'powershell' : 'posix';
5
+ const quote = (argument) => /^[a-z\d_./:@=-]+$/i.test(argument) ? argument
6
+ : `'${argument.replace(/'/g, shell === 'powershell' ? "''" : "'\\''")}'`;
7
+ const args = ['--data-dir', proposal.directory, 'limits', 'approve', proposal.id].map(quote).join(' ');
8
+ const pool = proposal.pool ?? current.pool;
9
+ return {
10
+ method: 'local-terminal',
11
+ instructions: 'Ask the user to review the changes and run one command in their own terminal. Use atto when the CLI is installed, or npx with Node.js/npm. Never run approval commands on the user\'s behalf. Proposing does not change active settings.',
12
+ changes: {
13
+ ...(current.access !== proposal.access ? { access: { from: current.access, to: proposal.access } } : {}),
14
+ ...(!isDeepStrictEqual(current.policy, proposal.policy) ? { policy: { from: current.policy, to: proposal.policy } } : {}),
15
+ ...(!isDeepStrictEqual(current.pool, pool) ? { pool: { from: current.pool, to: pool } } : {}),
16
+ },
17
+ shell,
18
+ commands: {
19
+ atto: `atto ${args}`,
20
+ npx: `npx --yes @attocash/mcp@latest ${args}`,
21
+ },
22
+ };
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@attocash/cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Atto wallet CLI and reusable wallet engine with local key custody and shared spending limits",
5
5
  "license": "BSD-3-Clause",
6
6
  "type": "module",