dealgo 1.0.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 (3) hide show
  1. package/README.md +301 -0
  2. package/dist/index.js +477 -0
  3. package/package.json +48 -0
package/README.md ADDED
@@ -0,0 +1,301 @@
1
+ # dealgo - DeAlgo CLI
2
+
3
+ **Git for AI Decisions** - Command-line interface for managing DeAlgo decisions, agents, and API keys.
4
+
5
+ ## Installation
6
+
7
+ ### Global Install (Recommended)
8
+ ```bash
9
+ npm install -g dealgo
10
+ ```
11
+
12
+ ### From Source
13
+ ```bash
14
+ cd cli
15
+ npm install
16
+ npm run build
17
+ npm link
18
+ ```
19
+
20
+ ### Direct Run (npx)
21
+ ```bash
22
+ npx dealgo status
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ### 1. Configure API Access
28
+ ```bash
29
+ # Set API key (via environment variable - most secure)
30
+ export DEALGO_API_KEY=dg_live_your_key_here
31
+
32
+ # Or store in config file (plaintext warning shown)
33
+ dealgo config set apiKey dg_live_your_key_here
34
+
35
+ # Set custom API URL (optional)
36
+ dealgo config set apiUrl https://dealgo-saas.vercel.app
37
+ ```
38
+
39
+ ### 2. Verify Connection
40
+ ```bash
41
+ dealgo status
42
+ ```
43
+
44
+ ### 3. Log Your First Decision
45
+ ```bash
46
+ dealgo decisions log \
47
+ --verdict APPROVE \
48
+ --score 85 \
49
+ --rationale "Feature request approved after safety review"
50
+ ```
51
+
52
+ ## Commands
53
+
54
+ ### System Status
55
+ ```bash
56
+ dealgo status # Check system health and tenant usage
57
+ dealgo status --json # JSON output for scripting
58
+ ```
59
+
60
+ ### Decision Management
61
+ ```bash
62
+ # Log a decision
63
+ dealgo decisions log \
64
+ --verdict APPROVE \
65
+ --score 90 \
66
+ --rationale "Safe to proceed" \
67
+ --agentId agent_123 # Optional: link to specific agent
68
+
69
+ # List recent decisions
70
+ dealgo decisions list --limit 20
71
+
72
+ # List in JSON format
73
+ dealgo decisions list --json
74
+ ```
75
+
76
+ **Supported Verdicts:** `APPROVE`, `DENY`, `DELAY`, `FOUNDER_REQUIRED`
77
+
78
+ ### Chain Verification
79
+ ```bash
80
+ dealgo verify-chain # Verify cryptographic chain integrity
81
+ dealgo verify-chain --json # JSON output
82
+ ```
83
+
84
+ ### API Key Management
85
+ ```bash
86
+ # List API keys
87
+ dealgo keys list
88
+
89
+ # Create new key with specific scopes
90
+ dealgo keys create \
91
+ --name "Production Key" \
92
+ --scopes "decisions:write,decisions:read,ops:read"
93
+
94
+ # Full access key (all scopes)
95
+ dealgo keys create \
96
+ --name "Admin Key" \
97
+ --scopes "decisions:write,decisions:read,ops:read,keys:manage"
98
+ ```
99
+
100
+ **Available Scopes:**
101
+ - `decisions:write` - Log decisions
102
+ - `decisions:read` - Read decisions
103
+ - `ops:read` - View system status
104
+ - `keys:manage` - Manage API keys
105
+
106
+ ### Configuration
107
+ ```bash
108
+ # Set values
109
+ dealgo config set apiUrl https://dealgo-saas.vercel.app
110
+ dealgo config set apiKey dg_live_...
111
+
112
+ # List all profiles
113
+ dealgo config list
114
+
115
+ # Switch profiles
116
+ dealgo config use production
117
+ dealgo config use local
118
+ ```
119
+
120
+ ## Profiles
121
+
122
+ DeAlgo CLI supports multiple environment profiles:
123
+
124
+ ```bash
125
+ # Use production (default)
126
+ dealgo status
127
+
128
+ # Use local development
129
+ dealgo --profile local status
130
+
131
+ # Create custom profile
132
+ dealgo --profile staging config set apiUrl https://staging.dealgo.com
133
+ dealgo --profile staging config set apiKey dg_test_...
134
+ dealgo --profile staging status
135
+ ```
136
+
137
+ **Config Location:** `~/.dealgo/config.json`
138
+
139
+ ## Environment Variables
140
+
141
+ Environment variables override config file values:
142
+
143
+ ```bash
144
+ export DEALGO_API_URL=https://dealgo-saas.vercel.app
145
+ export DEALGO_API_KEY=dg_live_your_key_here
146
+
147
+ dealgo status # Uses env vars
148
+ ```
149
+
150
+ ## Examples
151
+
152
+ ### E2E Governance Loop (Terminal Only)
153
+ ```bash
154
+ # 1. Check system health
155
+ dealgo status
156
+
157
+ # 2. Log a decision
158
+ dealgo decisions log \
159
+ --verdict APPROVE \
160
+ --score 92 \
161
+ --rationale "Code review passed, security scan clean"
162
+
163
+ # 3. Verify decision appears in log
164
+ dealgo decisions list --limit 5
165
+
166
+ # 4. Verify cryptographic chain
167
+ dealgo verify-chain
168
+ ```
169
+
170
+ ### CI/CD Integration (GitHub Actions)
171
+ ```yaml
172
+ name: Log Deployment Decision
173
+ on: [deployment]
174
+ jobs:
175
+ log:
176
+ runs-on: ubuntu-latest
177
+ steps:
178
+ - name: Log to DeAlgo
179
+ env:
180
+ DEALGO_API_KEY: ${{ secrets.DEALGO_API_KEY }}
181
+ run: |
182
+ npx dealgo decisions log \
183
+ --verdict APPROVE \
184
+ --score 95 \
185
+ --rationale "Deployment to production approved" \
186
+ --json
187
+ ```
188
+
189
+ ### JSON Output for Scripts
190
+ ```bash
191
+ # Get status as JSON
192
+ STATUS=$(dealgo status --json)
193
+ echo $STATUS | jq '.tenant.currentMonthUsage'
194
+
195
+ # Log decision and capture ID
196
+ RESULT=$(dealgo decisions log \
197
+ --verdict APPROVE \
198
+ --score 90 \
199
+ --rationale "Test" \
200
+ --json)
201
+ DECISION_ID=$(echo $RESULT | jq -r '.data.id')
202
+ echo "Logged decision: $DECISION_ID"
203
+ ```
204
+
205
+ ## Security Best Practices
206
+
207
+ ### ✅ Recommended
208
+ - Store API key in `DEALGO_API_KEY` environment variable
209
+ - Use OS-level secret management (AWS Secrets Manager, 1Password, etc.)
210
+ - Create scoped keys (minimum required permissions)
211
+ - Rotate keys regularly
212
+
213
+ ### ⚠️ Avoid
214
+ - Committing `~/.dealgo/config.json` to git
215
+ - Sharing API keys in plain text
216
+ - Using `*` scope unless necessary
217
+
218
+ ## Troubleshooting
219
+
220
+ ### Missing API Key
221
+ ```
222
+ ✗ Error: Missing API key
223
+ Set DEALGO_API_KEY environment variable or run:
224
+ dealgo config set apiKey dg_live_...
225
+ ```
226
+
227
+ **Solution:** Configure API key via environment variable or config file.
228
+
229
+ ### Invalid or Revoked Key
230
+ ```
231
+ ✗ Error: Invalid or revoked API key (401)
232
+ ```
233
+
234
+ **Solution:** Create a new key via web dashboard or `dealgo keys create`.
235
+
236
+ ### Insufficient Scope
237
+ ```
238
+ ✗ Error: Insufficient scope (403)
239
+ Your key needs: ops:read
240
+ ```
241
+
242
+ **Solution:** Create new key with required scopes:
243
+ ```bash
244
+ dealgo keys create --scopes "decisions:write,decisions:read,ops:read"
245
+ ```
246
+
247
+ ## Development
248
+
249
+ ### Build from Source
250
+ ```bash
251
+ cd cli
252
+ npm install
253
+ npm run build
254
+ ```
255
+
256
+ ### Local Testing
257
+ ```bash
258
+ npm run dev -- status
259
+ npm run dev -- decisions log --verdict APPROVE --score 90 --rationale "Test"
260
+ ```
261
+
262
+ ### Link for Global Use
263
+ ```bash
264
+ npm link
265
+ dealgo status
266
+ ```
267
+
268
+ ## License
269
+
270
+ Apache-2.0
271
+
272
+ ## Support
273
+
274
+ - **Docs:** https://dealgo-saas.vercel.app/developers
275
+ - **GitHub:** https://github.com/Jtjr86/ssi-protocol
276
+ - **Issues:** https://github.com/Jtjr86/ssi-protocol/issues
277
+
278
+ ssictl health
279
+ ssictl logs --follow
280
+ ```
281
+
282
+ ## Development
283
+
284
+ ```bash
285
+ # Clone repo
286
+ git clone https://github.com/Jtjr86/ssi-protocol.git
287
+ cd ssi-protocol/cli
288
+
289
+ # Install dependencies
290
+ npm install
291
+
292
+ # Run locally
293
+ npm run dev health
294
+
295
+ # Build
296
+ npm run build
297
+ ```
298
+
299
+ ## Documentation
300
+
301
+ Full docs at [ssi-protocol.org](https://ssi-protocol.org)
package/dist/index.js ADDED
@@ -0,0 +1,477 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * dealgo - DeAlgo CLI
5
+ * Git for AI Decisions
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ var __importDefault = (this && this.__importDefault) || function (mod) {
41
+ return (mod && mod.__esModule) ? mod : { "default": mod };
42
+ };
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ const commander_1 = require("commander");
45
+ const chalk_1 = __importDefault(require("chalk"));
46
+ const node_fetch_1 = __importDefault(require("node-fetch"));
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ const os = __importStar(require("os"));
50
+ const program = new commander_1.Command();
51
+ const CONFIG_DIR = path.join(os.homedir(), '.dealgo');
52
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
53
+ function ensureConfigDir() {
54
+ if (!fs.existsSync(CONFIG_DIR)) {
55
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
56
+ }
57
+ }
58
+ function loadConfig() {
59
+ ensureConfigDir();
60
+ if (!fs.existsSync(CONFIG_FILE)) {
61
+ const defaultConfig = {
62
+ defaultProfile: 'production',
63
+ profiles: {
64
+ production: {
65
+ apiUrl: 'https://dealgo-saas.vercel.app',
66
+ },
67
+ },
68
+ };
69
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(defaultConfig, null, 2));
70
+ return defaultConfig;
71
+ }
72
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
73
+ }
74
+ function saveConfig(config) {
75
+ ensureConfigDir();
76
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
77
+ }
78
+ function getProfile(profileName) {
79
+ const config = loadConfig();
80
+ const name = profileName || config.defaultProfile;
81
+ const profile = config.profiles[name];
82
+ if (!profile) {
83
+ console.error(chalk_1.default.red(`✗ Error: Profile "${name}" not found`));
84
+ console.log(chalk_1.default.gray(` Available profiles: ${Object.keys(config.profiles).join(', ')}`));
85
+ process.exit(1);
86
+ }
87
+ return profile;
88
+ }
89
+ function getApiKey(profile) {
90
+ // Priority: env var > config file
91
+ const apiKey = process.env.DEALGO_API_KEY || profile.apiKey;
92
+ if (!apiKey) {
93
+ console.error(chalk_1.default.red('✗ Error: Missing API key'));
94
+ console.log(chalk_1.default.gray(' Set DEALGO_API_KEY environment variable or run:'));
95
+ console.log(chalk_1.default.gray(' dealgo config set apiKey dg_live_...'));
96
+ process.exit(1);
97
+ }
98
+ return apiKey;
99
+ }
100
+ function getApiUrl(profile) {
101
+ return process.env.DEALGO_API_URL || profile.apiUrl;
102
+ }
103
+ // ============================================================================
104
+ // API Client
105
+ // ============================================================================
106
+ async function apiCall(endpoint, options = {}) {
107
+ const profile = getProfile(options.profile);
108
+ const apiKey = getApiKey(profile);
109
+ const apiUrl = getApiUrl(profile);
110
+ const url = `${apiUrl}${endpoint}`;
111
+ const headers = {
112
+ 'x-dealgo-key': apiKey,
113
+ };
114
+ if (options.body) {
115
+ headers['Content-Type'] = 'application/json';
116
+ }
117
+ try {
118
+ const response = await (0, node_fetch_1.default)(url, {
119
+ method: options.method || 'GET',
120
+ headers,
121
+ body: options.body ? JSON.stringify(options.body) : undefined,
122
+ });
123
+ const data = await response.json();
124
+ if (!response.ok) {
125
+ if (response.status === 401) {
126
+ console.error(chalk_1.default.red('✗ Error: Invalid or revoked API key (401)'));
127
+ console.log(chalk_1.default.gray(' Check your API key configuration'));
128
+ }
129
+ else if (response.status === 403) {
130
+ console.error(chalk_1.default.red('✗ Error: Insufficient scope (403)'));
131
+ const errorMsg = typeof data === 'object' && data.message ? data.message : '';
132
+ if (errorMsg) {
133
+ console.log(chalk_1.default.gray(` ${errorMsg}`));
134
+ }
135
+ }
136
+ else if (response.status === 429) {
137
+ console.error(chalk_1.default.red('✗ Error: Rate limit exceeded (429)'));
138
+ }
139
+ else {
140
+ console.error(chalk_1.default.red(`✗ Error: HTTP ${response.status}`));
141
+ if (typeof data === 'object' && data.error) {
142
+ console.log(chalk_1.default.gray(` ${data.error}`));
143
+ }
144
+ }
145
+ process.exit(1);
146
+ }
147
+ return data;
148
+ }
149
+ catch (error) {
150
+ console.error(chalk_1.default.red('✗ Error: Request failed'));
151
+ console.log(chalk_1.default.gray(` ${error.message}`));
152
+ console.log(chalk_1.default.gray(` URL: ${url}`));
153
+ process.exit(1);
154
+ }
155
+ }
156
+ // ============================================================================
157
+ // CLI Commands
158
+ // ============================================================================
159
+ program
160
+ .name('dealgo')
161
+ .description('CLI for DeAlgo - Git for AI Decisions')
162
+ .version('1.0.0')
163
+ .option('--profile <name>', 'Use specific profile');
164
+ // ----------------------------------------------------------------------------
165
+ // Status Command
166
+ // ----------------------------------------------------------------------------
167
+ program
168
+ .command('status')
169
+ .description('Check system health and tenant usage')
170
+ .option('--json', 'Output JSON format')
171
+ .action(async (options) => {
172
+ const data = await apiCall('/api/v1/ops/status', {
173
+ profile: program.opts().profile,
174
+ json: options.json
175
+ });
176
+ if (options.json) {
177
+ console.log(JSON.stringify(data, null, 2));
178
+ return;
179
+ }
180
+ console.log(chalk_1.default.green('✓ System Operational'));
181
+ // Health metrics
182
+ if (data.health) {
183
+ console.log(chalk_1.default.gray('\n Health:'));
184
+ if (data.health.db) {
185
+ console.log(chalk_1.default.gray(` - Database: ${data.health.db.connected ? 'Connected' : 'Disconnected'} (${data.health.db.latencyMs}ms)`));
186
+ }
187
+ if (data.health.upstash) {
188
+ console.log(chalk_1.default.gray(` - Upstash: ${data.health.upstash.reachable ? 'Reachable' : 'Unreachable'} (${data.health.upstash.latencyMs}ms)`));
189
+ }
190
+ }
191
+ // Usage statistics
192
+ if (data.usage) {
193
+ const u = data.usage;
194
+ console.log(chalk_1.default.gray('\n Usage:'));
195
+ console.log(chalk_1.default.gray(` - Plan: ${u.plan}`));
196
+ console.log(chalk_1.default.gray(` - Decisions logged: ${u.decisionsLogged}`));
197
+ console.log(chalk_1.default.gray(` - Quota remaining: ${u.quotaRemaining}`));
198
+ }
199
+ // Chain info
200
+ if (data.chain) {
201
+ console.log(chalk_1.default.gray('\n Chain:'));
202
+ console.log(chalk_1.default.gray(` - Total decisions: ${data.chain.totalDecisions}`));
203
+ console.log(chalk_1.default.gray(` - Head height: ${data.chain.headHeight}`));
204
+ }
205
+ if (data.tenant) {
206
+ const t = data.tenant;
207
+ console.log(chalk_1.default.gray('\n Your Tenant:'));
208
+ console.log(chalk_1.default.gray(` - Name: ${t.name || 'N/A'}`));
209
+ console.log(chalk_1.default.gray(` - Status: ${t.status || 'N/A'}`));
210
+ if (t.monthlyDecisionQuota !== undefined && t.currentMonthUsage !== undefined) {
211
+ const pct = ((t.currentMonthUsage / t.monthlyDecisionQuota) * 100).toFixed(1);
212
+ console.log(chalk_1.default.gray(` - Decisions: ${t.currentMonthUsage} / ${t.monthlyDecisionQuota} (${pct}%)`));
213
+ }
214
+ }
215
+ });
216
+ // ----------------------------------------------------------------------------
217
+ // Decisions Commands
218
+ // ----------------------------------------------------------------------------
219
+ const decisionsCmd = program
220
+ .command('decisions')
221
+ .description('Decision operations');
222
+ decisionsCmd
223
+ .command('log')
224
+ .description('Log a new decision')
225
+ .requiredOption('--verdict <type>', 'Verdict: APPROVE, DENY, DELAY, FOUNDER_REQUIRED')
226
+ .requiredOption('--score <number>', 'Survival score (-100 to 100)')
227
+ .requiredOption('--rationale <text>', 'Decision rationale')
228
+ .option('--agentId <id>', 'Optional agent ID')
229
+ .option('--json', 'Output JSON format')
230
+ .action(async (options) => {
231
+ const verdict = options.verdict.toUpperCase();
232
+ const validVerdicts = ['APPROVE', 'DENY', 'DELAY', 'FOUNDER_REQUIRED'];
233
+ if (!validVerdicts.includes(verdict)) {
234
+ console.error(chalk_1.default.red('✗ Error: Invalid verdict'));
235
+ console.log(chalk_1.default.gray(` Must be one of: ${validVerdicts.join(', ')}`));
236
+ process.exit(1);
237
+ }
238
+ const score = parseInt(options.score);
239
+ if (isNaN(score) || score < -100 || score > 100) {
240
+ console.error(chalk_1.default.red('✗ Error: Invalid score'));
241
+ console.log(chalk_1.default.gray(' Must be a number between -100 and 100'));
242
+ process.exit(1);
243
+ }
244
+ const body = {
245
+ verdict,
246
+ survivalScore: score,
247
+ rationale: [options.rationale],
248
+ };
249
+ if (options.agentId) {
250
+ body.agentId = options.agentId;
251
+ }
252
+ const data = await apiCall('/api/v1/decisions', {
253
+ method: 'POST',
254
+ body,
255
+ profile: program.opts().profile,
256
+ });
257
+ if (options.json) {
258
+ console.log(JSON.stringify(data, null, 2));
259
+ return;
260
+ }
261
+ if (data.ok && data.data) {
262
+ const d = data.data;
263
+ console.log(chalk_1.default.green('✓ Decision logged'));
264
+ console.log(chalk_1.default.gray(` ID: ${d.id || 'N/A'}`));
265
+ console.log(chalk_1.default.gray(` Chain Height: ${d.chainHeight || 'N/A'}`));
266
+ console.log(chalk_1.default.gray(` Hash: ${d.hash ? d.hash.substring(0, 16) + '...' : 'N/A'}`));
267
+ console.log(chalk_1.default.gray(` Timestamp: ${d.ts || 'N/A'}`));
268
+ }
269
+ });
270
+ decisionsCmd
271
+ .command('list')
272
+ .description('List recent decisions')
273
+ .option('--limit <number>', 'Number of decisions to show', '10')
274
+ .option('--json', 'Output JSON format')
275
+ .action(async (options) => {
276
+ const limit = parseInt(options.limit);
277
+ const data = await apiCall(`/api/v1/decisions?limit=${limit}`, {
278
+ profile: program.opts().profile,
279
+ });
280
+ if (options.json) {
281
+ console.log(JSON.stringify(data, null, 2));
282
+ return;
283
+ }
284
+ if (data.ok && data.data) {
285
+ const decisions = data.data;
286
+ if (decisions.length === 0) {
287
+ console.log(chalk_1.default.yellow('No decisions found'));
288
+ return;
289
+ }
290
+ console.log(chalk_1.default.bold('\nRecent Decisions:\n'));
291
+ console.log(chalk_1.default.gray('TIMESTAMP ') +
292
+ chalk_1.default.gray('VERDICT ') +
293
+ chalk_1.default.gray('SCORE ') +
294
+ chalk_1.default.gray('HEIGHT ') +
295
+ chalk_1.default.gray('HASH'));
296
+ console.log(chalk_1.default.gray('─'.repeat(80)));
297
+ decisions.forEach((d) => {
298
+ const ts = d.ts ? new Date(d.ts).toISOString().substring(0, 19) + 'Z' : 'N/A';
299
+ const verdict = (d.verdict || 'N/A').padEnd(8);
300
+ const score = String(d.survivalScore ?? 'N/A').padEnd(6);
301
+ const height = String(d.chainHeight ?? 'N/A').padEnd(7);
302
+ const hash = d.hash ? d.hash.substring(0, 16) : 'N/A';
303
+ console.log(`${ts} ${verdict} ${score} ${height} ${hash}`);
304
+ });
305
+ console.log(chalk_1.default.gray(`\nTotal: ${decisions.length} decisions`));
306
+ }
307
+ });
308
+ // ----------------------------------------------------------------------------
309
+ // Verify Chain Command
310
+ // ----------------------------------------------------------------------------
311
+ program
312
+ .command('verify-chain')
313
+ .description('Verify cryptographic chain integrity')
314
+ .option('--json', 'Output JSON format')
315
+ .action(async (options) => {
316
+ const data = await apiCall('/api/v1/verify-chain', {
317
+ profile: program.opts().profile,
318
+ });
319
+ if (options.json) {
320
+ console.log(JSON.stringify(data, null, 2));
321
+ return;
322
+ }
323
+ if (data.ok && data.data) {
324
+ const d = data.data;
325
+ if (d.verified) {
326
+ console.log(chalk_1.default.green('✓ Chain verified'));
327
+ }
328
+ else {
329
+ console.log(chalk_1.default.red('✗ Chain verification failed'));
330
+ }
331
+ console.log(chalk_1.default.gray(` Total Decisions: ${d.totalDecisions || 0}`));
332
+ console.log(chalk_1.default.gray(` Chain Height: ${d.chainHeight || 0}`));
333
+ if (d.headHash) {
334
+ console.log(chalk_1.default.gray(` Head Hash: ${d.headHash.substring(0, 16)}...`));
335
+ }
336
+ if (d.breaks && d.breaks.length > 0) {
337
+ console.log(chalk_1.default.red(`\n Chain Breaks Detected: ${d.breaks.length}`));
338
+ d.breaks.forEach((b, i) => {
339
+ console.log(chalk_1.default.red(` ${i + 1}. Height ${b.height}: ${b.reason}`));
340
+ });
341
+ }
342
+ else {
343
+ console.log(chalk_1.default.gray(' Breaks: 0'));
344
+ }
345
+ }
346
+ });
347
+ // ----------------------------------------------------------------------------
348
+ // Keys Commands
349
+ // ----------------------------------------------------------------------------
350
+ const keysCmd = program
351
+ .command('keys')
352
+ .description('API key management');
353
+ keysCmd
354
+ .command('list')
355
+ .description('List API keys')
356
+ .option('--json', 'Output JSON format')
357
+ .action(async (options) => {
358
+ const data = await apiCall('/api/v1/keys', {
359
+ profile: program.opts().profile,
360
+ });
361
+ if (options.json) {
362
+ console.log(JSON.stringify(data, null, 2));
363
+ return;
364
+ }
365
+ if (data.keys) {
366
+ if (data.keys.length === 0) {
367
+ console.log(chalk_1.default.yellow('No API keys found'));
368
+ return;
369
+ }
370
+ console.log(chalk_1.default.bold('\nAPI Keys:\n'));
371
+ console.log(chalk_1.default.gray('ID ') +
372
+ chalk_1.default.gray('NAME ') +
373
+ chalk_1.default.gray('LAST4 ') +
374
+ chalk_1.default.gray('CREATED'));
375
+ console.log(chalk_1.default.gray('─'.repeat(80)));
376
+ data.keys.forEach((k) => {
377
+ const id = (k.apiKeyId || 'N/A').padEnd(23);
378
+ const name = (k.name || 'Default Key').substring(0, 14).padEnd(14);
379
+ const last4 = (k.last4 || 'N/A').padEnd(7);
380
+ const created = k.createdAt ? new Date(k.createdAt).toISOString().substring(0, 10) : 'N/A';
381
+ console.log(`${id} ${name} ${last4} ${created}`);
382
+ });
383
+ console.log(chalk_1.default.gray(`\nTotal: ${data.keys.length} keys`));
384
+ }
385
+ });
386
+ keysCmd
387
+ .command('create')
388
+ .description('Create new API key')
389
+ .option('--name <name>', 'Key name', 'CLI Key')
390
+ .option('--scopes <scopes>', 'Comma-separated scopes', 'decisions:write,decisions:read,ops:read,keys:manage')
391
+ .option('--json', 'Output JSON format')
392
+ .action(async (options) => {
393
+ const scopes = options.scopes.split(',').map((s) => s.trim());
394
+ const data = await apiCall('/api/v1/keys', {
395
+ method: 'POST',
396
+ body: { scopes },
397
+ profile: program.opts().profile,
398
+ });
399
+ if (options.json) {
400
+ console.log(JSON.stringify(data, null, 2));
401
+ return;
402
+ }
403
+ if (data.apiKey) {
404
+ console.log(chalk_1.default.green('\n✓ API key created'));
405
+ console.log(chalk_1.default.yellow('\n⚠️ COPY THIS KEY NOW - It will not be shown again:'));
406
+ console.log(chalk_1.default.bold(`\n ${data.apiKey}\n`));
407
+ console.log(chalk_1.default.gray(` ID: ${data.apiKeyId || 'N/A'}`));
408
+ console.log(chalk_1.default.gray(` Scopes: ${data.scopes ? data.scopes.join(', ') : 'N/A'}`));
409
+ }
410
+ });
411
+ // ----------------------------------------------------------------------------
412
+ // Config Commands
413
+ // ----------------------------------------------------------------------------
414
+ const configCmd = program
415
+ .command('config')
416
+ .description('Manage configuration');
417
+ configCmd
418
+ .command('set')
419
+ .description('Set configuration value')
420
+ .argument('<key>', 'Configuration key (apiUrl, apiKey)')
421
+ .argument('<value>', 'Configuration value')
422
+ .action((key, value) => {
423
+ const config = loadConfig();
424
+ const profileName = program.opts().profile || config.defaultProfile;
425
+ if (!config.profiles[profileName]) {
426
+ config.profiles[profileName] = { apiUrl: '' };
427
+ }
428
+ if (key === 'apiUrl') {
429
+ config.profiles[profileName].apiUrl = value;
430
+ saveConfig(config);
431
+ console.log(chalk_1.default.green(`✓ Set ${key} = ${value} for profile "${profileName}"`));
432
+ }
433
+ else if (key === 'apiKey') {
434
+ console.log(chalk_1.default.yellow('⚠️ Warning: This will store your API key in plaintext'));
435
+ console.log(chalk_1.default.gray(` at ${CONFIG_FILE}`));
436
+ console.log(chalk_1.default.gray(' Use DEALGO_API_KEY environment variable for better security\n'));
437
+ config.profiles[profileName].apiKey = value;
438
+ saveConfig(config);
439
+ console.log(chalk_1.default.green(`✓ Set ${key} for profile "${profileName}"`));
440
+ }
441
+ else {
442
+ console.error(chalk_1.default.red(`✗ Error: Unknown configuration key "${key}"`));
443
+ console.log(chalk_1.default.gray(' Valid keys: apiUrl, apiKey'));
444
+ process.exit(1);
445
+ }
446
+ });
447
+ configCmd
448
+ .command('use')
449
+ .description('Switch default profile')
450
+ .argument('<profile>', 'Profile name')
451
+ .action((profileName) => {
452
+ const config = loadConfig();
453
+ if (!config.profiles[profileName]) {
454
+ console.error(chalk_1.default.red(`✗ Error: Profile "${profileName}" not found`));
455
+ console.log(chalk_1.default.gray(` Available: ${Object.keys(config.profiles).join(', ')}`));
456
+ process.exit(1);
457
+ }
458
+ config.defaultProfile = profileName;
459
+ saveConfig(config);
460
+ console.log(chalk_1.default.green(`✓ Switched to profile "${profileName}"`));
461
+ });
462
+ configCmd
463
+ .command('list')
464
+ .description('List all profiles')
465
+ .action(() => {
466
+ const config = loadConfig();
467
+ console.log(chalk_1.default.bold('\nProfiles:\n'));
468
+ Object.entries(config.profiles).forEach(([name, profile]) => {
469
+ const marker = name === config.defaultProfile ? chalk_1.default.green('* ') : ' ';
470
+ console.log(`${marker}${chalk_1.default.bold(name)}`);
471
+ console.log(chalk_1.default.gray(` API URL: ${profile.apiUrl}`));
472
+ console.log(chalk_1.default.gray(` API Key: ${profile.apiKey ? '***' + profile.apiKey.slice(-4) : 'Not set'}`));
473
+ });
474
+ console.log();
475
+ });
476
+ // Parse CLI arguments
477
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "dealgo",
3
+ "version": "1.0.0",
4
+ "description": "CLI for DeAlgo - Git for AI Decisions. Manage AI decisions, verify cryptographic chains, and control API keys from the terminal.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "dealgo": "./dist/index.js"
8
+ },
9
+ "homepage": "https://dealgo-saas.vercel.app",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "ts-node src/index.ts",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords": [
16
+ "dealgo",
17
+ "cli",
18
+ "ai",
19
+ "decisions",
20
+ "governance",
21
+ "audit"
22
+ ],
23
+ "author": "DeAlgo Contributors",
24
+ "license": "Apache-2.0",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/Jtjr86/ssi-protocol.git",
28
+ "directory": "cli"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md"
33
+ ],
34
+ "engines": {
35
+ "node": ">=16.0.0"
36
+ },
37
+ "dependencies": {
38
+ "commander": "^11.0.0",
39
+ "chalk": "^4.1.2",
40
+ "node-fetch": "^2.7.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^20.10.0",
44
+ "@types/node-fetch": "^2.6.9",
45
+ "typescript": "^5.6.0",
46
+ "ts-node": "^10.9.0"
47
+ }
48
+ }