@attocash/cli 0.2.1 → 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 +21 -2
- package/dist/application/app.js +11 -2
- package/dist/application/operations.js +2 -2
- package/dist/spending/approval.d.ts +30 -0
- package/dist/spending/approval.js +23 -0
- package/dist/wallet/signing.js +7 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -373,6 +373,12 @@ outstanding checks, including native credential probes and their subprocesses.
|
|
|
373
373
|
Doctor does not check npm for updates.
|
|
374
374
|
|
|
375
375
|
An agent should call the MCP **`doctor` tool** to inspect the server's own process.
|
|
376
|
+
`wallet_status.initialized` describes the saved public wallet identity; status
|
|
377
|
+
does not read the password store. If a signing operation returns
|
|
378
|
+
`WALLET_CREDENTIAL_MISSING`, the profile remains initialized but its credential
|
|
379
|
+
lookup returned empty. Run doctor in that session before attempting recovery;
|
|
380
|
+
do not reset or replace the wallet to repair credential access.
|
|
381
|
+
|
|
376
382
|
A terminal result cannot establish that an already-running MCP has the same
|
|
377
383
|
environment. When a user-owned Linux session socket is available, doctor may test
|
|
378
384
|
environment overrides in an isolated credential probe. It returns
|
|
@@ -427,8 +433,19 @@ atto --data-dir /absolute/path/to/profile limits set --access spend \
|
|
|
427
433
|
MCP `limits_propose` and generic `atto call limits_propose` **only propose** a change.
|
|
428
434
|
They cannot apply it. `limits_propose` also accepts `pool` with `indexes` and
|
|
429
435
|
`consolidate`; omitting it preserves the current pool. `limits_get` returns the
|
|
430
|
-
active policy, usage, `mcpAccess`, pool, and current proposal.
|
|
431
|
-
|
|
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:
|
|
432
449
|
|
|
433
450
|
```sh
|
|
434
451
|
atto --data-dir /absolute/path/to/profile limits approve PROPOSAL_ID
|
|
@@ -615,6 +632,8 @@ atto wallet configure --representative <atto-address>
|
|
|
615
632
|
Configuration updates preserve omitted settings. Supply at least one option.
|
|
616
633
|
`--representative` changes the default used to open accounts; use
|
|
617
634
|
`atto representative change <address>` to change an existing account's representative.
|
|
635
|
+
Selecting the account's current representative returns `REPRESENTATIVE_UNCHANGED`
|
|
636
|
+
without signing or publishing a transaction.
|
|
618
637
|
`wallet status` shows the profile directory and whether receiving runs in that
|
|
619
638
|
process; it does not report receiving sessions in other terminals.
|
|
620
639
|
|
package/dist/application/app.js
CHANGED
|
@@ -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';
|
|
@@ -173,8 +174,12 @@ export class AttoApplication {
|
|
|
173
174
|
this.recoveryReads++;
|
|
174
175
|
try {
|
|
175
176
|
const phrase = await this.secrets.get();
|
|
176
|
-
if (!phrase)
|
|
177
|
+
if (!phrase) {
|
|
178
|
+
this.requireResetFinished();
|
|
179
|
+
if (this.identity())
|
|
180
|
+
throw new AttoError('WALLET_CREDENTIAL_MISSING', 'The wallet is initialized, but the OS password store returned no recovery phrase for this profile. Run doctor in this session to check credential access. Do not reset or replace the wallet.');
|
|
177
181
|
throw new AttoError('WALLET_NOT_INITIALIZED', 'Create or import a wallet through the CLI first.');
|
|
182
|
+
}
|
|
178
183
|
return phrase;
|
|
179
184
|
}
|
|
180
185
|
finally {
|
|
@@ -575,7 +580,11 @@ export class AttoApplication {
|
|
|
575
580
|
throw new AttoError('JOURNAL_NOT_FOUND', 'This payment request is not in the local journal.');
|
|
576
581
|
return { record };
|
|
577
582
|
}
|
|
578
|
-
case 'limits_propose': return this.store.withWalletLock(async () =>
|
|
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
|
+
});
|
|
579
588
|
case 'metrics_get': return this.market.metrics();
|
|
580
589
|
case 'price_quote': return this.market.quoteUsd(args.amount);
|
|
581
590
|
case 'terms_get': return { ...marketTerms, accepted: this.store.get('market.terms')?.version === marketTerms.version };
|
|
@@ -69,9 +69,9 @@ export const operations = [
|
|
|
69
69
|
{ name: 'terms_accept', description: 'Persist acknowledgement of the specified terms version for USD-priced payments. Call only after the user has read and explicitly accepted those terms.', schema: z.strictObject({ version: z.string().min(1).max(128), accepted: z.literal(true) }), readOnly: false },
|
|
70
70
|
{ name: 'receive', description: 'Receive one pending payment by hash, opening the account if needed.', schema: z.strictObject({ index: index.default(0), hash, representative: address.optional() }), readOnly: false },
|
|
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
|
-
{ name: 'representative_change', description: 'Publish a representative change for an existing account.', schema: z.strictObject({ index: index.default(0), representative: address }), readOnly: false },
|
|
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
|
|
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/dist/wallet/signing.js
CHANGED
|
@@ -119,7 +119,13 @@ export async function signingWallet(seed, index, settings, beforeSign, suppliedW
|
|
|
119
119
|
},
|
|
120
120
|
async change(value, representative, timestamp) {
|
|
121
121
|
requireIndex(value);
|
|
122
|
-
return perform(time =>
|
|
122
|
+
return perform(time => {
|
|
123
|
+
const current = requireAccount();
|
|
124
|
+
if (current.representativeAddress.value === representative.value) {
|
|
125
|
+
throw new AttoError('REPRESENTATIVE_UNCHANGED', 'This account already uses the requested representative. Choose a different representative to publish a change.');
|
|
126
|
+
}
|
|
127
|
+
return attoAccountChange(current, representative, time);
|
|
128
|
+
}, timestamp);
|
|
123
129
|
},
|
|
124
130
|
async getAccountByIndex(value) { requireIndex(value); return account ?? null; },
|
|
125
131
|
close() { signer = undefined; },
|