dotdo 0.0.2 → 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 (313) hide show
  1. package/cli/README.md +238 -0
  2. package/cli/agent.ts +72 -0
  3. package/cli/bin.js +44 -0
  4. package/cli/bin.ts +38 -0
  5. package/cli/build.ts +157 -0
  6. package/cli/commands/auth/login.ts +14 -0
  7. package/cli/commands/auth/logout.ts +6 -0
  8. package/cli/commands/auth/whoami.ts +16 -0
  9. package/cli/commands/deploy-multi.ts +245 -0
  10. package/cli/commands/dev/deploy.ts +100 -0
  11. package/cli/commands/dev/dev.ts +95 -0
  12. package/cli/commands/dev/logs.ts +91 -0
  13. package/cli/commands/dev-local.ts +88 -0
  14. package/cli/commands/do-ops.ts +314 -0
  15. package/cli/commands/index.ts +100 -0
  16. package/cli/commands/init.ts +247 -0
  17. package/cli/commands/introspect/emitter.ts +315 -0
  18. package/cli/commands/introspect/index.ts +193 -0
  19. package/cli/commands/link.ts +598 -0
  20. package/cli/commands/snippets.ts +415 -0
  21. package/cli/commands/tunnel.ts +239 -0
  22. package/cli/device-auth.ts +289 -0
  23. package/cli/fallback.ts +12 -0
  24. package/cli/index.ts +121 -0
  25. package/cli/main.ts +246 -0
  26. package/cli/mcp-stdio.ts +790 -0
  27. package/cli/package.json +62 -0
  28. package/cli/runtime/do-registry.ts +193 -0
  29. package/cli/runtime/embedded-db.ts +344 -0
  30. package/cli/runtime/index.ts +9 -0
  31. package/cli/runtime/miniflare-adapter.ts +162 -0
  32. package/cli/sandbox.ts +82 -0
  33. package/cli/src/args.ts +174 -0
  34. package/cli/src/auth.ts +55 -0
  35. package/cli/src/commands/call.ts +84 -0
  36. package/cli/src/commands/charge.ts +96 -0
  37. package/cli/src/commands/config.ts +115 -0
  38. package/cli/src/commands/email.ts +112 -0
  39. package/cli/src/commands/llm.ts +115 -0
  40. package/cli/src/commands/queue.ts +134 -0
  41. package/cli/src/commands/text.ts +86 -0
  42. package/cli/src/config.ts +185 -0
  43. package/cli/src/output.ts +246 -0
  44. package/cli/src/rpc.ts +192 -0
  45. package/cli/utils/config.ts +282 -0
  46. package/cli/utils/detect.ts +73 -0
  47. package/cli/utils/index.ts +15 -0
  48. package/cli/utils/logger.ts +232 -0
  49. package/dist/ai/template-literals.js +2 -2
  50. package/dist/ai/template-literals.js.map +1 -1
  51. package/dist/api/middleware/auth.js +3 -2
  52. package/dist/api/middleware/auth.js.map +1 -1
  53. package/dist/db/iceberg/inverted-index.js +1 -1
  54. package/dist/db/iceberg/inverted-index.js.map +1 -1
  55. package/dist/db/iceberg/puffin.js.map +1 -1
  56. package/dist/db/json-indexes.js.map +1 -1
  57. package/dist/db/objects.js.map +1 -1
  58. package/dist/db/primitives/dag-scheduler/index.js +1 -1
  59. package/dist/db/primitives/dag-scheduler/index.js.map +1 -1
  60. package/dist/db/primitives/observability.js.map +1 -1
  61. package/dist/db/primitives/schema-evolution.js.map +1 -1
  62. package/dist/db/primitives/temporal-store.js.map +1 -1
  63. package/dist/db/primitives/typed-column-store.js.map +1 -1
  64. package/dist/db/primitives/utils/duration.js.map +1 -1
  65. package/dist/db/primitives/utils/murmur3.js +12 -14
  66. package/dist/db/primitives/utils/murmur3.js.map +1 -1
  67. package/dist/db/primitives/window-manager.js.map +1 -1
  68. package/dist/db/stores.js.map +1 -1
  69. package/dist/db/things.js.map +1 -1
  70. package/dist/lib/DODispatcher.js +2 -2
  71. package/dist/lib/DODispatcher.js.map +1 -1
  72. package/dist/lib/auto-wiring.js.map +1 -1
  73. package/dist/lib/channels/email.js +1 -1
  74. package/dist/lib/channels/email.js.map +1 -1
  75. package/dist/lib/channels/slack-blockkit.js.map +1 -1
  76. package/dist/lib/cloudflare/ai.js +1 -1
  77. package/dist/lib/cloudflare/ai.js.map +1 -1
  78. package/dist/lib/cloudflare/kv.js +1 -1
  79. package/dist/lib/cloudflare/kv.js.map +1 -1
  80. package/dist/lib/cloudflare/r2.js +3 -3
  81. package/dist/lib/cloudflare/r2.js.map +1 -1
  82. package/dist/lib/cloudflare/vectorize.js.map +1 -1
  83. package/dist/lib/cloudflare/workflows.js.map +1 -1
  84. package/dist/lib/executors/AgenticFunctionExecutor.js.map +1 -1
  85. package/dist/lib/executors/CodeFunctionExecutor.js.map +1 -1
  86. package/dist/lib/executors/GenerativeFunctionExecutor.js.map +1 -1
  87. package/dist/lib/executors/HumanFunctionExecutor.js +1 -1
  88. package/dist/lib/executors/HumanFunctionExecutor.js.map +1 -1
  89. package/dist/lib/executors/ParallelStepExecutor.js.map +1 -1
  90. package/dist/lib/experiments.js.map +1 -1
  91. package/dist/lib/flags/store.js.map +1 -1
  92. package/dist/lib/functions/FunctionComposition.js.map +1 -1
  93. package/dist/lib/functions/FunctionMiddleware.js.map +1 -1
  94. package/dist/lib/functions/FunctionRegistry.js.map +1 -1
  95. package/dist/lib/humans/templates.js.map +1 -1
  96. package/dist/lib/identity.js +2 -2
  97. package/dist/lib/identity.js.map +1 -1
  98. package/dist/lib/logging/index.js.map +1 -1
  99. package/dist/lib/mixins/bash.js +1 -73
  100. package/dist/lib/mixins/bash.js.map +1 -1
  101. package/dist/lib/mixins/git.js +0 -5
  102. package/dist/lib/mixins/git.js.map +1 -1
  103. package/dist/lib/mixins/npm.js.map +1 -1
  104. package/dist/lib/noun-id.js.map +1 -1
  105. package/dist/lib/rate-limit/sliding-window.js.map +1 -1
  106. package/dist/lib/rpc/bindings.js.map +1 -1
  107. package/dist/lib/safe-stringify.js.map +1 -1
  108. package/dist/lib/sandbox/miniflare-sandbox.js.map +1 -1
  109. package/dist/lib/sqids.js.map +1 -1
  110. package/dist/lib/sql/adapters/node-sql-parser.js.map +1 -1
  111. package/dist/lib/sql/adapters/pgsql-parser.js +19 -18
  112. package/dist/lib/sql/adapters/pgsql-parser.js.map +1 -1
  113. package/dist/metrics/hunch.js.map +1 -1
  114. package/dist/objects/API.js +1 -1
  115. package/dist/objects/API.js.map +1 -1
  116. package/dist/objects/Agent.js.map +1 -1
  117. package/dist/objects/Browser.js.map +1 -1
  118. package/dist/objects/CLI.js.map +1 -1
  119. package/dist/objects/DOBase.js.map +1 -1
  120. package/dist/objects/DOCache.js +153 -0
  121. package/dist/objects/DOCache.js.map +1 -0
  122. package/dist/objects/DOFull.js.map +1 -1
  123. package/dist/objects/Entity.js.map +1 -1
  124. package/dist/objects/Human.js.map +1 -1
  125. package/dist/objects/IcebergMetadataDO.js.map +1 -1
  126. package/dist/objects/IntegrationsDO.js.map +1 -1
  127. package/dist/objects/ObservabilityBroadcaster.js.map +1 -1
  128. package/dist/objects/Package.js.map +1 -1
  129. package/dist/objects/Product.js +1 -1
  130. package/dist/objects/Product.js.map +1 -1
  131. package/dist/objects/SaaS.js.map +1 -1
  132. package/dist/objects/SandboxDO.js.map +1 -1
  133. package/dist/objects/Service.js.map +1 -1
  134. package/dist/objects/VectorShardDO.js +9 -7
  135. package/dist/objects/VectorShardDO.js.map +1 -1
  136. package/dist/objects/Workflow.js.map +1 -1
  137. package/dist/objects/WorkflowFactory.js.map +1 -1
  138. package/dist/objects/WorkflowRuntime.js.map +1 -1
  139. package/dist/objects/lifecycle/Branch.js.map +1 -1
  140. package/dist/objects/lifecycle/Clone.js +1 -1
  141. package/dist/objects/lifecycle/Clone.js.map +1 -1
  142. package/dist/objects/lifecycle/Compact.js.map +1 -1
  143. package/dist/objects/lifecycle/Shard.js.map +1 -1
  144. package/dist/objects/persistence/checkpoint-manager.js.map +1 -1
  145. package/dist/objects/persistence/migration-runner.js.map +1 -1
  146. package/dist/objects/persistence/replication-manager.js +2 -2
  147. package/dist/objects/persistence/replication-manager.js.map +1 -1
  148. package/dist/objects/persistence/tiered-storage-manager.js.map +1 -1
  149. package/dist/objects/persistence/wal-manager.js.map +1 -1
  150. package/dist/objects/transport/auth-layer.js.map +1 -1
  151. package/dist/objects/transport/chain.js.map +1 -1
  152. package/dist/objects/transport/mcp-server.js +7 -6
  153. package/dist/objects/transport/mcp-server.js.map +1 -1
  154. package/dist/objects/transport/rest-autowire.js +3 -2
  155. package/dist/objects/transport/rest-autowire.js.map +1 -1
  156. package/dist/objects/transport/rest-router.js.map +1 -1
  157. package/dist/objects/transport/rpc-server.js +18 -15
  158. package/dist/objects/transport/rpc-server.js.map +1 -1
  159. package/dist/objects/transport/shared.js +2 -1
  160. package/dist/objects/transport/shared.js.map +1 -1
  161. package/dist/snippets/artifacts-ingest.js.map +1 -1
  162. package/dist/snippets/artifacts-serve.js.map +1 -1
  163. package/dist/snippets/search.js.map +1 -1
  164. package/dist/workflows/ScheduleManager.js.map +1 -1
  165. package/dist/workflows/StepResultStorage.js.map +1 -1
  166. package/dist/workflows/WaitForEventManager.js.map +1 -1
  167. package/dist/workflows/compat/backends/cloudflare-workflows.js.map +1 -1
  168. package/dist/workflows/compat/inngest/index.js.map +1 -1
  169. package/dist/workflows/compat/qstash/index.js.map +1 -1
  170. package/dist/workflows/compat/temporal/client.js.map +1 -1
  171. package/dist/workflows/compat/temporal/index.js.map +1 -1
  172. package/dist/workflows/compat/trigger/index.js.map +1 -1
  173. package/dist/workflows/compat/utils/index.js.map +1 -1
  174. package/dist/workflows/context/correlation.js +2 -2
  175. package/dist/workflows/context/correlation.js.map +1 -1
  176. package/dist/workflows/context/experiment.js +1 -1
  177. package/dist/workflows/context/experiment.js.map +1 -1
  178. package/dist/workflows/context/flag.js +1 -1
  179. package/dist/workflows/context/flag.js.map +1 -1
  180. package/dist/workflows/context/measure.js +1 -1
  181. package/dist/workflows/context/measure.js.map +1 -1
  182. package/dist/workflows/context/rate-limit.js.map +1 -1
  183. package/dist/workflows/data/entity-events/entity-events.js.map +1 -1
  184. package/dist/workflows/data/experiment/index.js.map +1 -1
  185. package/dist/workflows/data/goal/context.js +1 -1
  186. package/dist/workflows/data/goal/context.js.map +1 -1
  187. package/dist/workflows/data/measure/index.js +1 -1
  188. package/dist/workflows/data/measure/index.js.map +1 -1
  189. package/dist/workflows/data/stream/index.js +10 -76
  190. package/dist/workflows/data/stream/index.js.map +1 -1
  191. package/dist/workflows/data/track/context.js.map +1 -1
  192. package/dist/workflows/data/view/context.js.map +1 -1
  193. package/dist/workflows/domain.js.map +1 -1
  194. package/dist/workflows/flags.js +1 -1
  195. package/dist/workflows/flags.js.map +1 -1
  196. package/dist/workflows/hash.js.map +1 -1
  197. package/dist/workflows/on.js +1 -1
  198. package/dist/workflows/on.js.map +1 -1
  199. package/dist/workflows/schedule-builder.js.map +1 -1
  200. package/dist/workflows/visibility/index.js +0 -2
  201. package/dist/workflows/visibility/index.js.map +1 -1
  202. package/dist/workflows/visibility/query-parser.js.map +1 -1
  203. package/package.json +18 -3
  204. package/dist/api/analytics/router.js +0 -601
  205. package/dist/api/analytics/router.js.map +0 -1
  206. package/dist/api/index.js +0 -158
  207. package/dist/api/index.js.map +0 -1
  208. package/dist/api/middleware/error-handling.js +0 -176
  209. package/dist/api/middleware/error-handling.js.map +0 -1
  210. package/dist/api/middleware/request-id.js +0 -21
  211. package/dist/api/middleware/request-id.js.map +0 -1
  212. package/dist/api/pages.js +0 -1180
  213. package/dist/api/pages.js.map +0 -1
  214. package/dist/api/routes/api.js +0 -612
  215. package/dist/api/routes/api.js.map +0 -1
  216. package/dist/api/routes/browsers.js +0 -471
  217. package/dist/api/routes/browsers.js.map +0 -1
  218. package/dist/api/routes/do.js +0 -188
  219. package/dist/api/routes/do.js.map +0 -1
  220. package/dist/api/routes/mcp.js +0 -459
  221. package/dist/api/routes/mcp.js.map +0 -1
  222. package/dist/api/routes/obs.js +0 -445
  223. package/dist/api/routes/obs.js.map +0 -1
  224. package/dist/api/routes/openapi.js +0 -794
  225. package/dist/api/routes/openapi.js.map +0 -1
  226. package/dist/api/routes/rpc.js +0 -1103
  227. package/dist/api/routes/rpc.js.map +0 -1
  228. package/dist/api/routes/sandboxes.js +0 -389
  229. package/dist/api/routes/sandboxes.js.map +0 -1
  230. package/dist/api/test-do.js +0 -38
  231. package/dist/api/test-do.js.map +0 -1
  232. package/dist/api/types.js +0 -11
  233. package/dist/api/types.js.map +0 -1
  234. package/dist/cli/bin.js +0 -2
  235. package/dist/cli/main.js +0 -52342
  236. package/dist/do/bash.js +0 -35
  237. package/dist/do/bash.js.map +0 -1
  238. package/dist/do/fs.js +0 -25
  239. package/dist/do/fs.js.map +0 -1
  240. package/dist/do/full.js +0 -61
  241. package/dist/do/full.js.map +0 -1
  242. package/dist/do/git.js +0 -28
  243. package/dist/do/git.js.map +0 -1
  244. package/dist/do/index.js +0 -52
  245. package/dist/do/index.js.map +0 -1
  246. package/dist/lib/agent/tools/bash.js +0 -336
  247. package/dist/lib/agent/tools/bash.js.map +0 -1
  248. package/dist/lib/agent/tools/edit.js +0 -157
  249. package/dist/lib/agent/tools/edit.js.map +0 -1
  250. package/dist/lib/agent/tools/glob.js +0 -137
  251. package/dist/lib/agent/tools/glob.js.map +0 -1
  252. package/dist/lib/agent/tools/grep.js +0 -315
  253. package/dist/lib/agent/tools/grep.js.map +0 -1
  254. package/dist/lib/agent/tools/index.js +0 -71
  255. package/dist/lib/agent/tools/index.js.map +0 -1
  256. package/dist/lib/agent/tools/read.js +0 -212
  257. package/dist/lib/agent/tools/read.js.map +0 -1
  258. package/dist/lib/agent/tools/types.js +0 -197
  259. package/dist/lib/agent/tools/types.js.map +0 -1
  260. package/dist/lib/agent/tools/write.js +0 -159
  261. package/dist/lib/agent/tools/write.js.map +0 -1
  262. package/dist/lib/mixins/index.js +0 -29
  263. package/dist/lib/mixins/index.js.map +0 -1
  264. package/dist/primitives/bashx/src/ast/analyze.js +0 -1472
  265. package/dist/primitives/bashx/src/ast/analyze.js.map +0 -1
  266. package/dist/primitives/bashx/src/ast/parser.js +0 -1488
  267. package/dist/primitives/bashx/src/ast/parser.js.map +0 -1
  268. package/dist/primitives/bashx/src/do/commands/crypto.js +0 -1954
  269. package/dist/primitives/bashx/src/do/commands/crypto.js.map +0 -1
  270. package/dist/primitives/bashx/src/do/commands/data-processing.js +0 -1812
  271. package/dist/primitives/bashx/src/do/commands/data-processing.js.map +0 -1
  272. package/dist/primitives/bashx/src/do/commands/extended-utils.js +0 -804
  273. package/dist/primitives/bashx/src/do/commands/extended-utils.js.map +0 -1
  274. package/dist/primitives/bashx/src/do/commands/math-control.js +0 -1122
  275. package/dist/primitives/bashx/src/do/commands/math-control.js.map +0 -1
  276. package/dist/primitives/bashx/src/do/commands/posix-utils.js +0 -1015
  277. package/dist/primitives/bashx/src/do/commands/posix-utils.js.map +0 -1
  278. package/dist/primitives/bashx/src/do/commands/system-utils.js +0 -687
  279. package/dist/primitives/bashx/src/do/commands/system-utils.js.map +0 -1
  280. package/dist/primitives/bashx/src/do/commands/test-command.js +0 -523
  281. package/dist/primitives/bashx/src/do/commands/test-command.js.map +0 -1
  282. package/dist/primitives/bashx/src/do/commands/text-processing.js +0 -1550
  283. package/dist/primitives/bashx/src/do/commands/text-processing.js.map +0 -1
  284. package/dist/primitives/bashx/src/do/container-executor.js +0 -429
  285. package/dist/primitives/bashx/src/do/container-executor.js.map +0 -1
  286. package/dist/primitives/bashx/src/do/index.js +0 -668
  287. package/dist/primitives/bashx/src/do/index.js.map +0 -1
  288. package/dist/primitives/bashx/src/do/tiered-executor.js +0 -2647
  289. package/dist/primitives/bashx/src/do/tiered-executor.js.map +0 -1
  290. package/dist/primitives/bashx/src/do/worker.js +0 -352
  291. package/dist/primitives/bashx/src/do/worker.js.map +0 -1
  292. package/dist/primitives/bashx/src/types.js +0 -10
  293. package/dist/primitives/bashx/src/types.js.map +0 -1
  294. package/dist/primitives/fsx/core/backend.js +0 -480
  295. package/dist/primitives/fsx/core/backend.js.map +0 -1
  296. package/dist/primitives/fsx/core/constants.js +0 -140
  297. package/dist/primitives/fsx/core/constants.js.map +0 -1
  298. package/dist/primitives/fsx/core/fsx.js +0 -1184
  299. package/dist/primitives/fsx/core/fsx.js.map +0 -1
  300. package/dist/primitives/fsx/core/glob/glob.js +0 -438
  301. package/dist/primitives/fsx/core/glob/glob.js.map +0 -1
  302. package/dist/primitives/fsx/core/glob/index.js +0 -8
  303. package/dist/primitives/fsx/core/glob/index.js.map +0 -1
  304. package/dist/primitives/fsx/core/glob/match.js +0 -392
  305. package/dist/primitives/fsx/core/glob/match.js.map +0 -1
  306. package/dist/primitives/fsx/core/types.js +0 -307
  307. package/dist/primitives/fsx/core/types.js.map +0 -1
  308. package/dist/sdk/capnweb-compat.js +0 -42
  309. package/dist/sdk/capnweb-compat.js.map +0 -1
  310. package/dist/sdk/client.js +0 -20
  311. package/dist/sdk/client.js.map +0 -1
  312. package/dist/sdk/index.js +0 -17
  313. package/dist/sdk/index.js.map +0 -1
@@ -0,0 +1,185 @@
1
+ /**
2
+ * CLI Configuration Management
3
+ *
4
+ * Manages configuration stored in ~/.dotdo/config
5
+ */
6
+
7
+ import { readFile, writeFile, mkdir, access } from 'fs/promises'
8
+ import { homedir } from 'os'
9
+ import { join } from 'path'
10
+
11
+ // ============================================================================
12
+ // Constants
13
+ // ============================================================================
14
+
15
+ const CONFIG_DIR_NAME = '.dotdo'
16
+ const CONFIG_FILE_NAME = 'config'
17
+
18
+ // ============================================================================
19
+ // Types
20
+ // ============================================================================
21
+
22
+ export interface Config {
23
+ /** Base API URL for rpc.do gateway */
24
+ api_url: string
25
+ /** Output JSON instead of human-readable format */
26
+ json_output: boolean
27
+ /** Default LLM model */
28
+ default_model?: string
29
+ /** Default sender for calls/texts */
30
+ default_from?: string
31
+ /** Default email sender */
32
+ default_email_from?: string
33
+ /** Default currency for charges */
34
+ default_currency?: string
35
+ /** Custom headers to include in all requests */
36
+ custom_headers?: Record<string, string>
37
+ }
38
+
39
+ /** Available config keys for validation */
40
+ export const configKeys = [
41
+ 'api_url',
42
+ 'json_output',
43
+ 'default_model',
44
+ 'default_from',
45
+ 'default_email_from',
46
+ 'default_currency',
47
+ 'custom_headers',
48
+ ] as const
49
+
50
+ export type ConfigKey = (typeof configKeys)[number]
51
+
52
+ // ============================================================================
53
+ // Default Configuration
54
+ // ============================================================================
55
+
56
+ export const defaultConfig: Config = {
57
+ api_url: 'https://rpc.do',
58
+ json_output: false,
59
+ }
60
+
61
+ // ============================================================================
62
+ // Paths
63
+ // ============================================================================
64
+
65
+ /**
66
+ * Get the config directory path
67
+ */
68
+ function getConfigDir(): string {
69
+ return join(homedir(), CONFIG_DIR_NAME)
70
+ }
71
+
72
+ /**
73
+ * Get the config file path
74
+ */
75
+ export function getConfigPath(): string {
76
+ return join(getConfigDir(), CONFIG_FILE_NAME)
77
+ }
78
+
79
+ // ============================================================================
80
+ // Config Operations
81
+ // ============================================================================
82
+
83
+ /**
84
+ * Read stored configuration, merging with defaults
85
+ */
86
+ export async function getConfig(): Promise<Config> {
87
+ const configPath = getConfigPath()
88
+
89
+ try {
90
+ const content = await readFile(configPath, 'utf-8')
91
+ const stored = JSON.parse(content)
92
+
93
+ // Merge stored config with defaults
94
+ return {
95
+ ...defaultConfig,
96
+ ...stored,
97
+ }
98
+ } catch (error: unknown) {
99
+ // File doesn't exist or is malformed - return defaults
100
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
101
+ return { ...defaultConfig }
102
+ }
103
+ // JSON parse error - return defaults
104
+ return { ...defaultConfig }
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Update configuration values
110
+ */
111
+ export async function setConfig(updates: Partial<Config>): Promise<void> {
112
+ const configDir = getConfigDir()
113
+ const configPath = getConfigPath()
114
+
115
+ // Ensure config directory exists
116
+ try {
117
+ await access(configDir)
118
+ } catch {
119
+ await mkdir(configDir, { recursive: true, mode: 0o700 })
120
+ }
121
+
122
+ // Read existing config
123
+ let existing: Partial<Config> = {}
124
+ try {
125
+ const content = await readFile(configPath, 'utf-8')
126
+ existing = JSON.parse(content)
127
+ } catch {
128
+ // No existing config
129
+ }
130
+
131
+ // Merge and write
132
+ const merged = {
133
+ ...existing,
134
+ ...updates,
135
+ }
136
+
137
+ await writeFile(configPath, JSON.stringify(merged, null, 2), { mode: 0o600 })
138
+ }
139
+
140
+ /**
141
+ * Remove a configuration key
142
+ */
143
+ export async function unsetConfig(key: ConfigKey): Promise<void> {
144
+ const config = await getConfig()
145
+ // Use a type-safe delete
146
+ const configAny = config as unknown as Record<string, unknown>
147
+ delete configAny[key]
148
+ await setConfig(config)
149
+ }
150
+
151
+ /**
152
+ * Validate a config key exists
153
+ */
154
+ export function isValidConfigKey(key: string): key is ConfigKey {
155
+ return configKeys.includes(key as ConfigKey)
156
+ }
157
+
158
+ /**
159
+ * Parse and validate a config value by key
160
+ */
161
+ export function parseConfigValue(key: ConfigKey, value: string): unknown {
162
+ switch (key) {
163
+ case 'json_output':
164
+ if (value === 'true') return true
165
+ if (value === 'false') return false
166
+ throw new Error(`Invalid value for ${key}: expected 'true' or 'false'`)
167
+
168
+ case 'api_url':
169
+ case 'default_model':
170
+ case 'default_from':
171
+ case 'default_email_from':
172
+ case 'default_currency':
173
+ return value
174
+
175
+ case 'custom_headers':
176
+ try {
177
+ return JSON.parse(value)
178
+ } catch {
179
+ throw new Error(`Invalid value for ${key}: expected valid JSON`)
180
+ }
181
+
182
+ default:
183
+ return value
184
+ }
185
+ }
@@ -0,0 +1,246 @@
1
+ /**
2
+ * CLI Output Formatting
3
+ *
4
+ * Provides utilities for formatting CLI output:
5
+ * - JSON output for scripting
6
+ * - Table output for lists
7
+ * - Streaming output for LLM responses
8
+ * - Spinner for long-running operations
9
+ */
10
+
11
+ // ============================================================================
12
+ // Types
13
+ // ============================================================================
14
+
15
+ export interface OutputOptions {
16
+ /** Pretty print JSON */
17
+ pretty?: boolean
18
+ /** Columns to display in table */
19
+ columns?: string[]
20
+ /** Maximum width for truncation */
21
+ maxWidth?: number
22
+ }
23
+
24
+ export interface Spinner {
25
+ /** Start the spinner animation */
26
+ start(): void
27
+ /** Stop the spinner */
28
+ stop(): void
29
+ /** Stop with success message */
30
+ succeed(message?: string): void
31
+ /** Stop with failure message */
32
+ fail(message?: string): void
33
+ /** Update spinner text */
34
+ text(message: string): void
35
+ }
36
+
37
+ // ============================================================================
38
+ // JSON Formatting
39
+ // ============================================================================
40
+
41
+ /**
42
+ * Format data as JSON string
43
+ */
44
+ export function formatJson(data: unknown, options: OutputOptions = {}): string {
45
+ if (options.pretty) {
46
+ return JSON.stringify(data, null, 2)
47
+ }
48
+ return JSON.stringify(data)
49
+ }
50
+
51
+ // ============================================================================
52
+ // Table Formatting
53
+ // ============================================================================
54
+
55
+ /**
56
+ * Format array of objects as ASCII table
57
+ */
58
+ export function formatTable(
59
+ data: Record<string, unknown>[],
60
+ options: OutputOptions = {}
61
+ ): string {
62
+ if (data.length === 0) {
63
+ return ''
64
+ }
65
+
66
+ // Determine columns
67
+ const columns = options.columns ?? Object.keys(data[0])
68
+ const maxWidth = options.maxWidth ?? 50
69
+
70
+ // Calculate column widths
71
+ const widths: Record<string, number> = {}
72
+ for (const col of columns) {
73
+ widths[col] = col.length
74
+ for (const row of data) {
75
+ const value = String(row[col] ?? '')
76
+ widths[col] = Math.max(widths[col], Math.min(value.length, maxWidth))
77
+ }
78
+ }
79
+
80
+ // Format header
81
+ const header = columns.map((col) => col.padEnd(widths[col])).join(' ')
82
+ const separator = columns.map((col) => '-'.repeat(widths[col])).join(' ')
83
+
84
+ // Format rows
85
+ const rows = data.map((row) => {
86
+ return columns
87
+ .map((col) => {
88
+ const value = String(row[col] ?? '')
89
+ const truncated = value.length > maxWidth ? value.slice(0, maxWidth - 3) + '...' : value
90
+ return truncated.padEnd(widths[col])
91
+ })
92
+ .join(' ')
93
+ })
94
+
95
+ return [header, separator, ...rows].join('\n')
96
+ }
97
+
98
+ // ============================================================================
99
+ // Streaming Output
100
+ // ============================================================================
101
+
102
+ /**
103
+ * Async iterator for streaming response
104
+ */
105
+ export async function* formatStream(
106
+ stream: ReadableStream<Uint8Array>
107
+ ): AsyncGenerator<string, void, unknown> {
108
+ const reader = stream.getReader()
109
+ const decoder = new TextDecoder()
110
+
111
+ try {
112
+ while (true) {
113
+ const { done, value } = await reader.read()
114
+ if (done) break
115
+ yield decoder.decode(value, { stream: true })
116
+ }
117
+ } finally {
118
+ // releaseLock may not exist in all environments (e.g., mocks)
119
+ if (typeof reader.releaseLock === 'function') {
120
+ reader.releaseLock()
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Parse SSE stream and extract content
127
+ */
128
+ export async function* parseSSEStream(
129
+ stream: ReadableStream<Uint8Array>
130
+ ): AsyncGenerator<string, void, unknown> {
131
+ let buffer = ''
132
+
133
+ for await (const chunk of formatStream(stream)) {
134
+ buffer += chunk
135
+
136
+ // Process complete lines
137
+ const lines = buffer.split('\n')
138
+ buffer = lines.pop() ?? '' // Keep incomplete line in buffer
139
+
140
+ for (const line of lines) {
141
+ if (line.startsWith('data: ')) {
142
+ const data = line.slice(6)
143
+ if (data === '[DONE]') continue
144
+
145
+ try {
146
+ const parsed = JSON.parse(data)
147
+ if (parsed.delta) {
148
+ yield parsed.delta
149
+ } else if (parsed.content) {
150
+ yield parsed.content
151
+ } else if (parsed.choices?.[0]?.delta?.content) {
152
+ yield parsed.choices[0].delta.content
153
+ }
154
+ } catch {
155
+ // Not JSON, might be raw content
156
+ yield data
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ // Process any remaining buffer
163
+ if (buffer.startsWith('data: ')) {
164
+ const data = buffer.slice(6)
165
+ if (data !== '[DONE]') {
166
+ try {
167
+ const parsed = JSON.parse(data)
168
+ if (parsed.delta) {
169
+ yield parsed.delta
170
+ }
171
+ } catch {
172
+ yield data
173
+ }
174
+ }
175
+ }
176
+ }
177
+
178
+ // ============================================================================
179
+ // Spinner
180
+ // ============================================================================
181
+
182
+ const SPINNER_FRAMES = ['|', '/', '-', '\\']
183
+
184
+ /**
185
+ * Create a CLI spinner for long-running operations
186
+ */
187
+ export function createSpinner(message: string): Spinner {
188
+ let intervalId: ReturnType<typeof setInterval> | null = null
189
+ let frameIndex = 0
190
+ let currentMessage = message
191
+
192
+ const render = () => {
193
+ const frame = SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length]
194
+ process.stdout.write(`\r${frame} ${currentMessage}`)
195
+ frameIndex++
196
+ }
197
+
198
+ const clear = () => {
199
+ process.stdout.write('\r' + ' '.repeat(currentMessage.length + 3) + '\r')
200
+ }
201
+
202
+ return {
203
+ start() {
204
+ if (intervalId) return
205
+ render()
206
+ intervalId = setInterval(render, 100)
207
+ },
208
+
209
+ stop() {
210
+ if (intervalId) {
211
+ clearInterval(intervalId)
212
+ intervalId = null
213
+ }
214
+ clear()
215
+ },
216
+
217
+ succeed(msg?: string) {
218
+ this.stop()
219
+ console.log(`\u2713 ${msg ?? currentMessage}`)
220
+ },
221
+
222
+ fail(msg?: string) {
223
+ this.stop()
224
+ console.log(`\u2717 ${msg ?? currentMessage}`)
225
+ },
226
+
227
+ text(msg: string) {
228
+ currentMessage = msg
229
+ },
230
+ }
231
+ }
232
+
233
+ // ============================================================================
234
+ // Color Output (if terminal supports it)
235
+ // ============================================================================
236
+
237
+ const supportsColor = process.stdout.isTTY && process.env.TERM !== 'dumb'
238
+
239
+ export const colors = {
240
+ red: (text: string) => (supportsColor ? `\x1b[31m${text}\x1b[0m` : text),
241
+ green: (text: string) => (supportsColor ? `\x1b[32m${text}\x1b[0m` : text),
242
+ yellow: (text: string) => (supportsColor ? `\x1b[33m${text}\x1b[0m` : text),
243
+ blue: (text: string) => (supportsColor ? `\x1b[34m${text}\x1b[0m` : text),
244
+ dim: (text: string) => (supportsColor ? `\x1b[2m${text}\x1b[0m` : text),
245
+ bold: (text: string) => (supportsColor ? `\x1b[1m${text}\x1b[0m` : text),
246
+ }
package/cli/src/rpc.ts ADDED
@@ -0,0 +1,192 @@
1
+ /**
2
+ * CLI RPC Client
3
+ *
4
+ * Makes authenticated requests to *.do services via rpc.do gateway.
5
+ */
6
+
7
+ import { getConfig } from './config'
8
+ import { getAuthHeaders, AuthError } from './auth'
9
+
10
+ // ============================================================================
11
+ // Types
12
+ // ============================================================================
13
+
14
+ export interface RPCError {
15
+ error: string
16
+ message?: string
17
+ code?: string
18
+ }
19
+
20
+ export interface RPCRequestOptions {
21
+ /** HTTP method */
22
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
23
+ /** Request body (will be JSON stringified) */
24
+ body?: unknown
25
+ /** Additional headers */
26
+ headers?: Record<string, string>
27
+ /** Stream the response */
28
+ stream?: boolean
29
+ }
30
+
31
+ export class RPCNetworkError extends Error {
32
+ constructor(message: string) {
33
+ super(`Network error: ${message}`)
34
+ this.name = 'RPCNetworkError'
35
+ }
36
+ }
37
+
38
+ export class RPCAPIError extends Error {
39
+ status: number
40
+ code?: string
41
+
42
+ constructor(message: string, status: number, code?: string) {
43
+ super(message)
44
+ this.name = 'RPCAPIError'
45
+ this.status = status
46
+ this.code = code
47
+ }
48
+ }
49
+
50
+ export class RPCRateLimitError extends RPCAPIError {
51
+ retryAfter?: number
52
+
53
+ constructor(message: string, retryAfter?: number) {
54
+ super(message, 429, 'rate_limit_exceeded')
55
+ this.name = 'RPCRateLimitError'
56
+ this.retryAfter = retryAfter
57
+ }
58
+ }
59
+
60
+ // ============================================================================
61
+ // RPC Client
62
+ // ============================================================================
63
+
64
+ /**
65
+ * Make an authenticated request to a *.do service
66
+ */
67
+ export async function rpcRequest<T = unknown>(
68
+ service: string,
69
+ path: string,
70
+ options: RPCRequestOptions = {}
71
+ ): Promise<T> {
72
+ const config = await getConfig()
73
+ const authHeaders = await getAuthHeaders()
74
+
75
+ const { method = 'GET', body, headers = {}, stream = false } = options
76
+
77
+ const url = `${config.api_url}/${service}${path}`
78
+
79
+ const requestHeaders: Record<string, string> = {
80
+ ...authHeaders,
81
+ ...headers,
82
+ }
83
+
84
+ // Only set Content-Type if not already provided and body is defined
85
+ if (body !== undefined && !requestHeaders['Content-Type']) {
86
+ requestHeaders['Content-Type'] = 'application/json'
87
+ }
88
+
89
+ let response: Response
90
+ try {
91
+ response = await fetch(url, {
92
+ method,
93
+ headers: requestHeaders,
94
+ body: body !== undefined ? JSON.stringify(body) : undefined,
95
+ })
96
+ } catch (error) {
97
+ throw new RPCNetworkError((error as Error).message)
98
+ }
99
+
100
+ // Handle rate limiting
101
+ if (response.status === 429) {
102
+ const retryAfter = response.headers.get('Retry-After')
103
+ throw new RPCRateLimitError(
104
+ 'Rate limit exceeded. Please try again later.',
105
+ retryAfter ? parseInt(retryAfter, 10) : undefined
106
+ )
107
+ }
108
+
109
+ // Handle errors
110
+ if (!response.ok) {
111
+ let errorData: RPCError
112
+ try {
113
+ errorData = await response.json()
114
+ } catch {
115
+ errorData = { error: 'unknown_error', message: `Request failed with status ${response.status}` }
116
+ }
117
+
118
+ throw new RPCAPIError(
119
+ errorData.message || errorData.error,
120
+ response.status,
121
+ errorData.code || errorData.error
122
+ )
123
+ }
124
+
125
+ // Stream response
126
+ if (stream && response.body) {
127
+ return response.body as unknown as T
128
+ }
129
+
130
+ // JSON response
131
+ return response.json()
132
+ }
133
+
134
+ /**
135
+ * Make a request to calls.do
136
+ */
137
+ export async function callsRequest<T = unknown>(
138
+ path: string,
139
+ options?: RPCRequestOptions
140
+ ): Promise<T> {
141
+ return rpcRequest<T>('calls.do', path, options)
142
+ }
143
+
144
+ /**
145
+ * Make a request to texts.do
146
+ */
147
+ export async function textsRequest<T = unknown>(
148
+ path: string,
149
+ options?: RPCRequestOptions
150
+ ): Promise<T> {
151
+ return rpcRequest<T>('texts.do', path, options)
152
+ }
153
+
154
+ /**
155
+ * Make a request to emails.do
156
+ */
157
+ export async function emailsRequest<T = unknown>(
158
+ path: string,
159
+ options?: RPCRequestOptions
160
+ ): Promise<T> {
161
+ return rpcRequest<T>('emails.do', path, options)
162
+ }
163
+
164
+ /**
165
+ * Make a request to payments.do
166
+ */
167
+ export async function paymentsRequest<T = unknown>(
168
+ path: string,
169
+ options?: RPCRequestOptions
170
+ ): Promise<T> {
171
+ return rpcRequest<T>('payments.do', path, options)
172
+ }
173
+
174
+ /**
175
+ * Make a request to queue.do
176
+ */
177
+ export async function queueRequest<T = unknown>(
178
+ path: string,
179
+ options?: RPCRequestOptions
180
+ ): Promise<T> {
181
+ return rpcRequest<T>('queue.do', path, options)
182
+ }
183
+
184
+ /**
185
+ * Make a request to llm.do
186
+ */
187
+ export async function llmRequest<T = unknown>(
188
+ path: string,
189
+ options?: RPCRequestOptions
190
+ ): Promise<T> {
191
+ return rpcRequest<T>('llm.do', path, options)
192
+ }