@reppo/cli 0.1.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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -0
  3. package/dist/api/platform.d.ts +39 -0
  4. package/dist/api/platform.d.ts.map +1 -0
  5. package/dist/api/platform.js +148 -0
  6. package/dist/api/platform.js.map +1 -0
  7. package/dist/bin.d.ts +3 -0
  8. package/dist/bin.d.ts.map +1 -0
  9. package/dist/bin.js +60 -0
  10. package/dist/bin.js.map +1 -0
  11. package/dist/chain/abis.d.ts +576 -0
  12. package/dist/chain/abis.d.ts.map +1 -0
  13. package/dist/chain/abis.js +66 -0
  14. package/dist/chain/abis.js.map +1 -0
  15. package/dist/chain/addresses.d.ts +27 -0
  16. package/dist/chain/addresses.d.ts.map +1 -0
  17. package/dist/chain/addresses.js +39 -0
  18. package/dist/chain/addresses.js.map +1 -0
  19. package/dist/chain/clients.d.ts +46 -0
  20. package/dist/chain/clients.d.ts.map +1 -0
  21. package/dist/chain/clients.js +48 -0
  22. package/dist/chain/clients.js.map +1 -0
  23. package/dist/chain/contracts.d.ts +28 -0
  24. package/dist/chain/contracts.d.ts.map +1 -0
  25. package/dist/chain/contracts.js +70 -0
  26. package/dist/chain/contracts.js.map +1 -0
  27. package/dist/chain/errors.d.ts +14 -0
  28. package/dist/chain/errors.d.ts.map +1 -0
  29. package/dist/chain/errors.js +72 -0
  30. package/dist/chain/errors.js.map +1 -0
  31. package/dist/commands/_base.d.ts +19 -0
  32. package/dist/commands/_base.d.ts.map +1 -0
  33. package/dist/commands/_base.js +40 -0
  34. package/dist/commands/_base.js.map +1 -0
  35. package/dist/commands/auth.d.ts +9 -0
  36. package/dist/commands/auth.d.ts.map +1 -0
  37. package/dist/commands/auth.js +93 -0
  38. package/dist/commands/auth.js.map +1 -0
  39. package/dist/commands/claim-emissions.d.ts +11 -0
  40. package/dist/commands/claim-emissions.d.ts.map +1 -0
  41. package/dist/commands/claim-emissions.js +192 -0
  42. package/dist/commands/claim-emissions.js.map +1 -0
  43. package/dist/commands/extend-lock.d.ts +11 -0
  44. package/dist/commands/extend-lock.d.ts.map +1 -0
  45. package/dist/commands/extend-lock.js +196 -0
  46. package/dist/commands/extend-lock.js.map +1 -0
  47. package/dist/commands/grant-access.d.ts +11 -0
  48. package/dist/commands/grant-access.d.ts.map +1 -0
  49. package/dist/commands/grant-access.js +191 -0
  50. package/dist/commands/grant-access.js.map +1 -0
  51. package/dist/commands/lock.d.ts +11 -0
  52. package/dist/commands/lock.d.ts.map +1 -0
  53. package/dist/commands/lock.js +192 -0
  54. package/dist/commands/lock.js.map +1 -0
  55. package/dist/commands/mint-pod.d.ts +15 -0
  56. package/dist/commands/mint-pod.d.ts.map +1 -0
  57. package/dist/commands/mint-pod.js +286 -0
  58. package/dist/commands/mint-pod.js.map +1 -0
  59. package/dist/commands/query/balance.d.ts +9 -0
  60. package/dist/commands/query/balance.d.ts.map +1 -0
  61. package/dist/commands/query/balance.js +91 -0
  62. package/dist/commands/query/balance.js.map +1 -0
  63. package/dist/commands/query/datanet.d.ts +10 -0
  64. package/dist/commands/query/datanet.d.ts.map +1 -0
  65. package/dist/commands/query/datanet.js +128 -0
  66. package/dist/commands/query/datanet.js.map +1 -0
  67. package/dist/commands/query/emissions-due.d.ts +18 -0
  68. package/dist/commands/query/emissions-due.d.ts.map +1 -0
  69. package/dist/commands/query/emissions-due.js +170 -0
  70. package/dist/commands/query/emissions-due.js.map +1 -0
  71. package/dist/commands/query/pod.d.ts +8 -0
  72. package/dist/commands/query/pod.d.ts.map +1 -0
  73. package/dist/commands/query/pod.js +88 -0
  74. package/dist/commands/query/pod.js.map +1 -0
  75. package/dist/commands/query/voting-power.d.ts +9 -0
  76. package/dist/commands/query/voting-power.d.ts.map +1 -0
  77. package/dist/commands/query/voting-power.js +82 -0
  78. package/dist/commands/query/voting-power.js.map +1 -0
  79. package/dist/commands/register-agent.d.ts +9 -0
  80. package/dist/commands/register-agent.d.ts.map +1 -0
  81. package/dist/commands/register-agent.js +115 -0
  82. package/dist/commands/register-agent.js.map +1 -0
  83. package/dist/commands/unlock.d.ts +11 -0
  84. package/dist/commands/unlock.d.ts.map +1 -0
  85. package/dist/commands/unlock.js +181 -0
  86. package/dist/commands/unlock.js.map +1 -0
  87. package/dist/commands/vote.d.ts +13 -0
  88. package/dist/commands/vote.d.ts.map +1 -0
  89. package/dist/commands/vote.js +174 -0
  90. package/dist/commands/vote.js.map +1 -0
  91. package/dist/config/load.d.ts +19 -0
  92. package/dist/config/load.d.ts.map +1 -0
  93. package/dist/config/load.js +66 -0
  94. package/dist/config/load.js.map +1 -0
  95. package/dist/output/format.d.ts +43 -0
  96. package/dist/output/format.d.ts.map +1 -0
  97. package/dist/output/format.js +58 -0
  98. package/dist/output/format.js.map +1 -0
  99. package/dist/state/db.d.ts +47 -0
  100. package/dist/state/db.d.ts.map +1 -0
  101. package/dist/state/db.js +169 -0
  102. package/dist/state/db.js.map +1 -0
  103. package/dist/state/idempotency.d.ts +66 -0
  104. package/dist/state/idempotency.d.ts.map +1 -0
  105. package/dist/state/idempotency.js +173 -0
  106. package/dist/state/idempotency.js.map +1 -0
  107. package/package.json +63 -0
@@ -0,0 +1,19 @@
1
+ import type { Network } from '../chain/addresses.js';
2
+ export interface Config {
3
+ network: Network;
4
+ rpcUrl: string | undefined;
5
+ /**
6
+ * Reppo platform API base URL. Mainnet defaults to https://api.reppo.xyz.
7
+ * Testnet has no stable hosted endpoint yet — operators MUST set
8
+ * REPPO_API_URL when network=testnet (commands that don't talk to the
9
+ * platform API ignore this).
10
+ */
11
+ apiUrl: string | undefined;
12
+ apiKey: string | undefined;
13
+ privateKey: `0x${string}` | undefined;
14
+ voterPrivateKey: `0x${string}` | undefined;
15
+ }
16
+ export declare function loadConfig(overrides?: {
17
+ network?: Network;
18
+ }): Config;
19
+ //# sourceMappingURL=load.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../src/config/load.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAGrD,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B;;;;;OAKG;IACH,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,UAAU,EAAE,KAAK,MAAM,EAAE,GAAG,SAAS,CAAC;IACtC,eAAe,EAAE,KAAK,MAAM,EAAE,GAAG,SAAS,CAAC;CAC5C;AAmCD,wBAAgB,UAAU,CAAC,SAAS,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CAoCxE"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Layered config:
3
+ * 1. CLI flags (handled by clipanion at the command level)
4
+ * 2. .reppo.json in cwd
5
+ * 3. ~/.reppo/config.json
6
+ * 4. Environment variables
7
+ * 5. Built-in defaults
8
+ *
9
+ * Each value records its source so --debug can print "REPPO_NETWORK=mainnet
10
+ * (from env)".
11
+ */
12
+ import { readFileSync, existsSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ import { homedir } from 'node:os';
15
+ import { cliError } from '../output/format.js';
16
+ const DEFAULT_API_MAINNET = 'https://api.reppo.xyz';
17
+ function readJsonIfExists(path) {
18
+ if (!existsSync(path))
19
+ return {};
20
+ try {
21
+ return JSON.parse(readFileSync(path, 'utf-8'));
22
+ }
23
+ catch {
24
+ return {};
25
+ }
26
+ }
27
+ function normalizePk(raw) {
28
+ if (!raw)
29
+ return undefined;
30
+ const trimmed = raw.trim();
31
+ if (trimmed.startsWith('0x') && /^0x[0-9a-fA-F]{64}$/.test(trimmed)) {
32
+ return trimmed;
33
+ }
34
+ if (/^[0-9a-fA-F]{64}$/.test(trimmed)) {
35
+ return `0x${trimmed}`;
36
+ }
37
+ throw cliError('INVALID_PRIVATE_KEY', 'Private key must be a 32-byte hex string (0x-prefixed or bare 64 hex chars).', 'Check that REPPO_PRIVATE_KEY (or REPPO_VOTER_PRIVATE_KEY) is exactly 64 hex chars, optionally 0x-prefixed.');
38
+ }
39
+ export function loadConfig(overrides = {}) {
40
+ const cwd = readJsonIfExists(join(process.cwd(), '.reppo.json'));
41
+ const home = readJsonIfExists(join(homedir(), '.reppo', 'config.json'));
42
+ const networkRaw = overrides.network ??
43
+ process.env.REPPO_NETWORK ??
44
+ cwd.network ??
45
+ home.network ??
46
+ 'mainnet';
47
+ if (networkRaw !== 'mainnet' && networkRaw !== 'testnet') {
48
+ throw cliError('INVALID_NETWORK', `Invalid network "${networkRaw}" — must be "mainnet" or "testnet".`, 'Set REPPO_NETWORK=mainnet (default) or REPPO_NETWORK=testnet, or pass --network <name>.');
49
+ }
50
+ const network = networkRaw;
51
+ const apiUrlRaw = process.env.REPPO_API_URL ??
52
+ cwd.apiUrl ??
53
+ home.apiUrl ??
54
+ (network === 'mainnet' ? DEFAULT_API_MAINNET : undefined);
55
+ // Strip trailing slash so callers can append `${apiUrl}/me/agents` cleanly.
56
+ const apiUrl = apiUrlRaw?.replace(/\/+$/, '');
57
+ return {
58
+ network,
59
+ rpcUrl: process.env.REPPO_RPC_URL ?? cwd.rpcUrl ?? home.rpcUrl,
60
+ apiUrl,
61
+ apiKey: process.env.REPPO_API_KEY,
62
+ privateKey: normalizePk(process.env.REPPO_PRIVATE_KEY),
63
+ voterPrivateKey: normalizePk(process.env.REPPO_VOTER_PRIVATE_KEY),
64
+ };
65
+ }
66
+ //# sourceMappingURL=load.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/config/load.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAiB/C,MAAM,mBAAmB,GAAG,uBAAuB,CAAC;AAQpD,SAAS,gBAAgB,CAAC,IAAY;IACpC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAc,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,GAAuB;IAC1C,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACpE,OAAO,OAAwB,CAAC;IAClC,CAAC;IACD,IAAI,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,OAAO,KAAK,OAAO,EAAE,CAAC;IACxB,CAAC;IACD,MAAM,QAAQ,CACZ,qBAAqB,EACrB,8EAA8E,EAC9E,4GAA4G,CAC7G,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,YAAmC,EAAE;IAC9D,MAAM,GAAG,GAAG,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC;IAExE,MAAM,UAAU,GACd,SAAS,CAAC,OAAO;QACjB,OAAO,CAAC,GAAG,CAAC,aAAa;QACzB,GAAG,CAAC,OAAO;QACX,IAAI,CAAC,OAAO;QACZ,SAAS,CAAC;IAEZ,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QACzD,MAAM,QAAQ,CACZ,iBAAiB,EACjB,oBAAoB,UAAU,qCAAqC,EACnE,yFAAyF,CAC1F,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAY,UAAU,CAAC;IAEpC,MAAM,SAAS,GACb,OAAO,CAAC,GAAG,CAAC,aAAa;QACzB,GAAG,CAAC,MAAM;QACV,IAAI,CAAC,MAAM;QACX,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC5D,4EAA4E;IAC5E,MAAM,MAAM,GAAG,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAE9C,OAAO;QACL,OAAO;QACP,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM;QAC9D,MAAM;QACN,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa;QACjC,UAAU,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;QACtD,eAAe,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC;KAClE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Output formatting for the CLI. Two modes:
3
+ * - human (default): printed via console.log, friendly multi-line
4
+ * - json: single JSON object on stdout per command
5
+ *
6
+ * Errors ALWAYS emit JSON on stderr regardless of mode, with a stable
7
+ * `code` field agents can match on. Human mode also prints a friendly
8
+ * line to stderr above the JSON for terminal users.
9
+ */
10
+ export type OutputMode = 'human' | 'json';
11
+ export interface CliError {
12
+ code: string;
13
+ message: string;
14
+ hint?: string;
15
+ details?: Record<string, unknown>;
16
+ }
17
+ export interface FormatContext {
18
+ mode: OutputMode;
19
+ }
20
+ export declare function setOutputMode(mode: OutputMode): void;
21
+ export declare function getOutputMode(): OutputMode;
22
+ /**
23
+ * Build a thrown error with the CLI's stable `code` field (and optional
24
+ * `hint`) attached. Replaces the repeated
25
+ * `Object.assign(new Error(msg), { code, hint })` pattern across commands.
26
+ *
27
+ * `hint` is only attached when supplied — under `exactOptionalPropertyTypes`,
28
+ * `{ hint: undefined }` and "no hint" are distinct, and `fail()` only renders
29
+ * the hint when the property is actually present.
30
+ */
31
+ export declare function cliError(code: string, message: string, hint?: string): Error & {
32
+ code: string;
33
+ hint?: string;
34
+ };
35
+ /** Emit a successful command result. */
36
+ export declare function emit(payload: Record<string, unknown>, humanLines?: string[]): void;
37
+ /**
38
+ * Emit a structured error to stderr and exit non-zero. Always JSON on
39
+ * stderr — agents reading stderr always get parseable output, regardless
40
+ * of mode.
41
+ */
42
+ export declare function fail(err: CliError, exitCode?: number): never;
43
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/output/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1C,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,CAAC;CAClB;AAID,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAEpD;AAED,wBAAgB,aAAa,IAAI,UAAU,CAE1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAG9G;AAED,wCAAwC;AACxC,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAUlF;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,SAAI,GAAG,KAAK,CAOvD"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Output formatting for the CLI. Two modes:
3
+ * - human (default): printed via console.log, friendly multi-line
4
+ * - json: single JSON object on stdout per command
5
+ *
6
+ * Errors ALWAYS emit JSON on stderr regardless of mode, with a stable
7
+ * `code` field agents can match on. Human mode also prints a friendly
8
+ * line to stderr above the JSON for terminal users.
9
+ */
10
+ let activeMode = 'human';
11
+ export function setOutputMode(mode) {
12
+ activeMode = mode;
13
+ }
14
+ export function getOutputMode() {
15
+ return activeMode;
16
+ }
17
+ /**
18
+ * Build a thrown error with the CLI's stable `code` field (and optional
19
+ * `hint`) attached. Replaces the repeated
20
+ * `Object.assign(new Error(msg), { code, hint })` pattern across commands.
21
+ *
22
+ * `hint` is only attached when supplied — under `exactOptionalPropertyTypes`,
23
+ * `{ hint: undefined }` and "no hint" are distinct, and `fail()` only renders
24
+ * the hint when the property is actually present.
25
+ */
26
+ export function cliError(code, message, hint) {
27
+ const err = new Error(message);
28
+ return Object.assign(err, hint !== undefined ? { code, hint } : { code });
29
+ }
30
+ /** Emit a successful command result. */
31
+ export function emit(payload, humanLines) {
32
+ if (activeMode === 'json') {
33
+ process.stdout.write(JSON.stringify(payload) + '\n');
34
+ return;
35
+ }
36
+ if (humanLines) {
37
+ for (const line of humanLines)
38
+ process.stdout.write(line + '\n');
39
+ }
40
+ else {
41
+ process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
42
+ }
43
+ }
44
+ /**
45
+ * Emit a structured error to stderr and exit non-zero. Always JSON on
46
+ * stderr — agents reading stderr always get parseable output, regardless
47
+ * of mode.
48
+ */
49
+ export function fail(err, exitCode = 1) {
50
+ if (activeMode === 'human') {
51
+ process.stderr.write(`✗ ${err.code}: ${err.message}\n`);
52
+ if (err.hint)
53
+ process.stderr.write(` hint: ${err.hint}\n`);
54
+ }
55
+ process.stderr.write(JSON.stringify({ error: err }) + '\n');
56
+ process.exit(exitCode);
57
+ }
58
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/output/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAeH,IAAI,UAAU,GAAe,OAAO,CAAC;AAErC,MAAM,UAAU,aAAa,CAAC,IAAgB;IAC5C,UAAU,GAAG,IAAI,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,OAAe,EAAE,IAAa;IACnE,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,IAAI,CAAC,OAAgC,EAAE,UAAqB;IAC1E,IAAI,UAAU,KAAK,MAAM,EAAE,CAAC;QAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;QACrD,OAAO;IACT,CAAC;IACD,IAAI,UAAU,EAAE,CAAC;QACf,KAAK,MAAM,IAAI,IAAI,UAAU;YAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACnE,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,IAAI,CAAC,GAAa,EAAE,QAAQ,GAAG,CAAC;IAC9C,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;QAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;QACxD,IAAI,GAAG,CAAC,IAAI;YAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IAC5D,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACzB,CAAC"}
@@ -0,0 +1,47 @@
1
+ export type IdempotencyStatus = 'pending' | 'submitted' | 'confirmed' | 'failed';
2
+ export interface IdempotencyEntry {
3
+ command: string;
4
+ /**
5
+ * Deterministic SHA-256 of the canonicalized command args (e.g.
6
+ * `{podId, subnetId, like}` for vote). Stored on first write so the
7
+ * cache can detect a key being reused for a different intent within
8
+ * the same command — `IDEMPOTENCY_COMMAND_MISMATCH` only catches
9
+ * cross-command reuse, not (vote pod 39 like) vs (vote pod 41 dislike)
10
+ * under one key.
11
+ */
12
+ argsFingerprint: string;
13
+ status: IdempotencyStatus;
14
+ result: Record<string, unknown>;
15
+ txHash: string | null;
16
+ createdAt: number;
17
+ updatedAt: number;
18
+ }
19
+ export interface SessionEntry {
20
+ /** Optional — only set for /api/v1/agents/* persistent tokens. */
21
+ agentId?: string;
22
+ accessToken: string;
23
+ walletAddress: string | null;
24
+ /**
25
+ * Optional — set for api.reppo.xyz wallet-auth sessions (24h tokens).
26
+ * Epoch ms. Callers should treat the session as expired when
27
+ * Date.now() >= expiresAt.
28
+ */
29
+ expiresAt?: number;
30
+ createdAt: number;
31
+ }
32
+ export declare function getIdempotent(key: string): Promise<IdempotencyEntry | null>;
33
+ /**
34
+ * Insert OR update an idempotency entry under the lock. Allowed
35
+ * transitions:
36
+ * - first write (no prior entry): always allowed
37
+ * - same command, same argsFingerprint, non-terminal status: allowed
38
+ * - different command for the same key: rejected (caller bug)
39
+ * - same command, different argsFingerprint: rejected (caller bug —
40
+ * reusing one key across different intents would silently return
41
+ * the wrong cached result)
42
+ * - confirmed → anything: rejected (final state)
43
+ */
44
+ export declare function upsertIdempotent(key: string, entry: IdempotencyEntry): Promise<void>;
45
+ export declare function getSession(network: string, name: string): Promise<SessionEntry | null>;
46
+ export declare function saveSession(network: string, name: string, entry: SessionEntry): Promise<void>;
47
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/state/db.ts"],"names":[],"mappings":"AAuBA,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,QAAQ,CAAC;AAEjF,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAmGD,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAGjF;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuC1F;AAID,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAG5F;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAKnG"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Tiny JSON-file store at ~/.reppo/cli-state.json. Used for the
3
+ * idempotency cache and platform-API session tokens. Pure JS — no native
4
+ * deps. Concurrent CLI invocations are serialized via proper-lockfile.
5
+ *
6
+ * Schema:
7
+ * {
8
+ * idempotency: { [key]: { command, status, result, txHash, createdAt, updatedAt } },
9
+ * sessions: { [`${network}:${name}`]: { agentId, accessToken, walletAddress, createdAt } }
10
+ * }
11
+ *
12
+ * Concurrency model: every read-modify-write goes through `withLockedState`
13
+ * which acquires a proper-lockfile lock on the state file path BEFORE the
14
+ * read. Concurrent invocations queue. The state file is touched (created
15
+ * empty) under the same lock to avoid the lockfile-on-nonexistent-file
16
+ * footgun.
17
+ */
18
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync, renameSync } from 'node:fs';
19
+ import { dirname } from 'node:path';
20
+ import { homedir } from 'node:os';
21
+ import lockfile from 'proper-lockfile';
22
+ import { cliError } from '../output/format.js';
23
+ function statePath() {
24
+ return process.env.REPPO_STATE_PATH ?? `${homedir()}/.reppo/cli-state.json`;
25
+ }
26
+ function emptyState() {
27
+ return { idempotency: {}, sessions: {} };
28
+ }
29
+ /**
30
+ * Ensure the state file exists with 0o600 perms. Idempotent. The
31
+ * proper-lockfile library refuses to lock a non-existent path, so we
32
+ * have to create it before requesting the lock.
33
+ */
34
+ function ensureFile(path) {
35
+ // Owner-only on the parent dir too, not just the state file. mkdirSync
36
+ // without a mode would default to 0o777 & ~umask (typically 0o755) —
37
+ // which lets anyone on a shared system enumerate ~/.reppo/ and read
38
+ // any quarantine files we leave behind.
39
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
40
+ try {
41
+ chmodSync(dirname(path), 0o700);
42
+ }
43
+ catch { /* best-effort */ }
44
+ if (!existsSync(path)) {
45
+ writeFileSync(path, JSON.stringify(emptyState()), { mode: 0o600 });
46
+ return;
47
+ }
48
+ // Re-assert 0o600 on every access. `mode` in writeFileSync only takes
49
+ // effect on creation; if a user ever chmodded the file, this fixes it.
50
+ try {
51
+ chmodSync(path, 0o600);
52
+ }
53
+ catch { /* best-effort */ }
54
+ }
55
+ function readUnsafe(path) {
56
+ const raw = readFileSync(path, 'utf-8');
57
+ try {
58
+ const parsed = JSON.parse(raw);
59
+ return {
60
+ idempotency: parsed.idempotency ?? {},
61
+ sessions: parsed.sessions ?? {},
62
+ };
63
+ }
64
+ catch (err) {
65
+ // Corrupt state — quarantine the bad file and start fresh, but
66
+ // surface the corruption to the user. Silently dropping records
67
+ // would defeat the whole idempotency contract.
68
+ const quarantine = `${path}.corrupt-${Date.now()}`;
69
+ try {
70
+ renameSync(path, quarantine);
71
+ // rename preserves perms but be defensive — the source file's
72
+ // mode might have drifted before the corruption.
73
+ chmodSync(quarantine, 0o600);
74
+ }
75
+ catch { /* best-effort */ }
76
+ process.stderr.write(`[reppo-cli] WARNING: state file ${path} was corrupt and has been ` +
77
+ `moved to ${quarantine}. Idempotency cache reset. Original error: ${err.message}\n`);
78
+ return emptyState();
79
+ }
80
+ }
81
+ function writeUnsafe(path, s) {
82
+ writeFileSync(path, JSON.stringify(s, null, 2), { mode: 0o600 });
83
+ try {
84
+ chmodSync(path, 0o600);
85
+ }
86
+ catch { /* best-effort */ }
87
+ }
88
+ /**
89
+ * Acquire the state-file lock, run `mutator(currentState)`, persist its
90
+ * return value, release the lock. The mutator runs inside the critical
91
+ * section so reads + decisions + writes are atomic.
92
+ */
93
+ async function withLockedState(mutator) {
94
+ const path = statePath();
95
+ ensureFile(path);
96
+ const release = await lockfile.lock(path, {
97
+ retries: { retries: 10, minTimeout: 50, maxTimeout: 500, factor: 2 },
98
+ stale: 10_000,
99
+ });
100
+ try {
101
+ const current = readUnsafe(path);
102
+ const next = await mutator(current);
103
+ writeUnsafe(path, next.state);
104
+ return next.result;
105
+ }
106
+ finally {
107
+ await release();
108
+ }
109
+ }
110
+ /** Read-only state access. Acquires the lock briefly. */
111
+ async function readLocked() {
112
+ return withLockedState((s) => ({ state: s, result: s }));
113
+ }
114
+ // ── Idempotency API ────────────────────────────────────────────────────
115
+ export async function getIdempotent(key) {
116
+ const s = await readLocked();
117
+ return s.idempotency[key] ?? null;
118
+ }
119
+ /**
120
+ * Insert OR update an idempotency entry under the lock. Allowed
121
+ * transitions:
122
+ * - first write (no prior entry): always allowed
123
+ * - same command, same argsFingerprint, non-terminal status: allowed
124
+ * - different command for the same key: rejected (caller bug)
125
+ * - same command, different argsFingerprint: rejected (caller bug —
126
+ * reusing one key across different intents would silently return
127
+ * the wrong cached result)
128
+ * - confirmed → anything: rejected (final state)
129
+ */
130
+ export async function upsertIdempotent(key, entry) {
131
+ await withLockedState((s) => {
132
+ const prior = s.idempotency[key];
133
+ if (prior) {
134
+ if (prior.command !== entry.command) {
135
+ throw cliError('IDEMPOTENCY_COMMAND_MISMATCH', `idempotency key "${key}" was previously used by command "${prior.command}", ` +
136
+ `not "${entry.command}". Use a unique key per (command, intent) pair.`, 'Use a key like "<command>-<intent>-<id>" (e.g. "vote-pod-34-like") so commands cannot collide.');
137
+ }
138
+ if (prior.argsFingerprint !== entry.argsFingerprint) {
139
+ // Pre-v0.2 entries lack argsFingerprint — render a placeholder so
140
+ // a legacy entry produces a clean structured error here too,
141
+ // rather than crashing on `.slice()` of undefined. Normal command
142
+ // flow catches legacy entries earlier via IDEMPOTENCY_LEGACY_ENTRY,
143
+ // but `upsertIdempotent` is a public API and may be called directly.
144
+ const priorFp = prior.argsFingerprint ? prior.argsFingerprint.slice(0, 12) : '(legacy)';
145
+ throw cliError('IDEMPOTENCY_ARGS_MISMATCH', `idempotency key "${key}" was previously used with different args ` +
146
+ `(fingerprint ${priorFp}…) and is now being reused with new args ` +
147
+ `(fingerprint ${entry.argsFingerprint.slice(0, 12)}…).`, 'A given idempotency key represents a single intent. Use a fresh key when the args change (e.g. different --pod or --like value).');
148
+ }
149
+ if (prior.status === 'confirmed') {
150
+ throw cliError('IDEMPOTENCY_TERMINAL_STATE', `idempotency key "${key}" is already in terminal state "confirmed"; ` +
151
+ `refusing to overwrite. Use a new key for a new attempt.`, 'A confirmed key is final. Use a fresh --idempotency-key for a new attempt.');
152
+ }
153
+ }
154
+ s.idempotency[key] = { ...entry, updatedAt: Date.now() };
155
+ return { state: s, result: undefined };
156
+ });
157
+ }
158
+ // ── Session API ────────────────────────────────────────────────────────
159
+ export async function getSession(network, name) {
160
+ const s = await readLocked();
161
+ return s.sessions[`${network}:${name}`] ?? null;
162
+ }
163
+ export async function saveSession(network, name, entry) {
164
+ await withLockedState((s) => {
165
+ s.sessions[`${network}:${name}`] = entry;
166
+ return { state: s, result: undefined };
167
+ });
168
+ }
169
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/state/db.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ import { type IdempotencyEntry, type IdempotencyStatus } from './db.js';
2
+ export type { IdempotencyEntry, IdempotencyStatus };
3
+ /**
4
+ * Decision returned by `peekIdempotent` so write commands can decide what
5
+ * to do *before* spending gas:
6
+ * - 'proceed' — no cache hit (or pre-submit failure); do the work.
7
+ * - 'return-confirmed' — cache has a final result; emit it and skip.
8
+ * - 'return-submitted' — cache has a tx hash that hasn't confirmed yet;
9
+ * emit it and tell the caller to poll the explorer.
10
+ * Two terminal "do not retry" states (`pending`, `failed-after-broadcast`)
11
+ * throw inside the helper because they never make sense to handle inline.
12
+ */
13
+ export type CacheDecision<R> = {
14
+ kind: 'proceed';
15
+ } | {
16
+ kind: 'return-confirmed';
17
+ result: R;
18
+ txHash: string | null;
19
+ } | {
20
+ kind: 'return-submitted';
21
+ result: R;
22
+ txHash: string | null;
23
+ };
24
+ /**
25
+ * SHA-256 of the canonicalized JSON of the args object. Object keys are
26
+ * sorted so {a:1,b:2} and {b:2,a:1} fingerprint identically; bigints
27
+ * are stringified so they survive JSON.stringify; nested arrays/objects
28
+ * recurse. The output is the only thing the cache compares — small
29
+ * representation choices below matter for matching across runs.
30
+ */
31
+ export declare function fingerprintArgs(args: Record<string, unknown>): string;
32
+ export declare function getIdempotent(key: string, command: string, args: Record<string, unknown>): Promise<IdempotencyEntry | null>;
33
+ /**
34
+ * Inspect the idempotency cache and decide whether the caller should
35
+ * proceed with a write. Lifts the cache-decision logic that previously
36
+ * lived inline in `vote.ts` so every write command shares the same
37
+ * policy:
38
+ * - dry-run: never consult or mutate the cache (sim is read-only;
39
+ * persisting a "submitted" record would let a sim poison the cache
40
+ * for the next real call).
41
+ * - confirmed: short-circuit with the cached result.
42
+ * - submitted: short-circuit with the cached tx hash; user polls.
43
+ * - pending: refuse — another invocation is mid-flight.
44
+ * - failed + txHash: refuse — re-broadcasting under the same key would
45
+ * pay gas for a second doomed attempt; force a fresh key.
46
+ * - failed + no txHash: pre-submit failure, safe to retry: 'proceed'.
47
+ *
48
+ * The result type `R` matches the shape the command stores via
49
+ * `markConfirmed` (e.g. `{ txHash, podId, ..., basescanUrl }` for vote).
50
+ * Cached results are typed as the caller's `R`; runtime shape is whatever
51
+ * the original `markConfirmed` recorded.
52
+ */
53
+ export declare function peekIdempotent<R>(key: string | undefined, command: string, args: Record<string, unknown>, isDryRun: boolean): Promise<CacheDecision<R>>;
54
+ export declare function begin(key: string, command: string, args: Record<string, unknown>): Promise<void>;
55
+ export declare function markSubmitted(key: string, command: string, args: Record<string, unknown>, txHash: string): Promise<void>;
56
+ export declare function markConfirmed(key: string, command: string, args: Record<string, unknown>, result: Record<string, unknown>, txHash: string): Promise<void>;
57
+ /**
58
+ * Mark an idempotency entry as failed. Pass `txHash` when the failure
59
+ * happened *after* the tx was broadcast (e.g. on-chain revert, receipt
60
+ * timeout) so the cached entry retains the hash for forensics + so the
61
+ * "same-key retry on failed" guard in callers can distinguish pre-submit
62
+ * failures (safe to retry) from post-submit failures (must not retry —
63
+ * the tx already executed and may have side-effects).
64
+ */
65
+ export declare function markFailed(key: string, command: string, args: Record<string, unknown>, error: string, txHash?: string | null): Promise<void>;
66
+ //# sourceMappingURL=idempotency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../../src/state/idempotency.ts"],"names":[],"mappings":"AA2BA,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,SAAS,CAAC;AAGjB,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,CAAC;AAEpD;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,IACvB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC9D;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAEnE;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAcrE;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA8BlC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,cAAc,CAAC,CAAC,EACpC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CA8B3B;AAED,wBAAsB,KAAK,CACzB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,IAAI,CAAC,CAWf;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAWf;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAWf;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,MAAM,GAAG,IAAW,GAC3B,OAAO,CAAC,IAAI,CAAC,CAWf"}