@mod-computer/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.
- package/README.md +125 -0
- package/commands/execute.md +156 -0
- package/commands/overview.md +233 -0
- package/commands/review.md +151 -0
- package/commands/spec.md +169 -0
- package/dist/app.js +227 -0
- package/dist/cli.bundle.js +25824 -0
- package/dist/cli.bundle.js.map +7 -0
- package/dist/cli.js +121 -0
- package/dist/commands/agents-run.js +71 -0
- package/dist/commands/auth.js +151 -0
- package/dist/commands/branch.js +1411 -0
- package/dist/commands/claude-sync.js +772 -0
- package/dist/commands/index.js +43 -0
- package/dist/commands/init.js +378 -0
- package/dist/commands/recover.js +207 -0
- package/dist/commands/spec.js +386 -0
- package/dist/commands/status.js +329 -0
- package/dist/commands/sync.js +95 -0
- package/dist/commands/workspace.js +423 -0
- package/dist/components/conflict-resolution-ui.js +120 -0
- package/dist/components/messages.js +5 -0
- package/dist/components/thread.js +8 -0
- package/dist/config/features.js +72 -0
- package/dist/config/release-profiles/development.json +11 -0
- package/dist/config/release-profiles/mvp.json +12 -0
- package/dist/config/release-profiles/v0.1.json +11 -0
- package/dist/config/release-profiles/v0.2.json +11 -0
- package/dist/containers/branches-container.js +140 -0
- package/dist/containers/directory-container.js +92 -0
- package/dist/containers/thread-container.js +214 -0
- package/dist/containers/threads-container.js +27 -0
- package/dist/containers/workspaces-container.js +27 -0
- package/dist/daemon-worker.js +257 -0
- package/dist/lib/auth-server.js +153 -0
- package/dist/lib/browser.js +35 -0
- package/dist/lib/storage.js +203 -0
- package/dist/services/automatic-file-tracker.js +303 -0
- package/dist/services/cli-orchestrator.js +227 -0
- package/dist/services/feature-flags.js +187 -0
- package/dist/services/file-import-service.js +283 -0
- package/dist/services/file-transformation-service.js +218 -0
- package/dist/services/logger.js +44 -0
- package/dist/services/mod-config.js +61 -0
- package/dist/services/modignore-service.js +326 -0
- package/dist/services/sync-daemon.js +244 -0
- package/dist/services/thread-notification-service.js +50 -0
- package/dist/services/thread-service.js +147 -0
- package/dist/stores/use-directory-store.js +96 -0
- package/dist/stores/use-threads-store.js +46 -0
- package/dist/stores/use-workspaces-store.js +32 -0
- package/dist/types/config.js +16 -0
- package/dist/types/index.js +2 -0
- package/dist/types/workspace-connection.js +2 -0
- package/dist/types.js +1 -0
- package/package.json +67 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import dotenv from 'dotenv';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { render } from 'ink';
|
|
7
|
+
import App from './app.js';
|
|
8
|
+
import { repo as getRepo } from '@mod/mod-core/repos/repo.node';
|
|
9
|
+
import { buildCommandRegistry } from './commands/index.js';
|
|
10
|
+
import meow from 'meow';
|
|
11
|
+
// Background watch functionality removed - using automatic-file-tracker instead
|
|
12
|
+
import { getFeatureFlags } from './services/feature-flags.js';
|
|
13
|
+
import { FEATURES, isFeatureEnabled } from './config/features.js';
|
|
14
|
+
// Load environment variables from multiple candidate locations
|
|
15
|
+
// 1) CWD (project dir where user runs `mod`)
|
|
16
|
+
dotenv.config({ override: false, debug: false, quiet: true });
|
|
17
|
+
// 2) Package-local .env (e.g., packages/mod-cli/.env) for monorepo development
|
|
18
|
+
try {
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = path.dirname(__filename);
|
|
21
|
+
// dist/cli.js -> ../.env
|
|
22
|
+
const pkgEnv = path.join(__dirname, '../.env');
|
|
23
|
+
dotenv.config({ path: pkgEnv, override: true, debug: false, quiet: true });
|
|
24
|
+
// monorepo root .env (two levels up from packages/mod-cli/dist)
|
|
25
|
+
const rootEnv = path.join(__dirname, '../../../.env');
|
|
26
|
+
dotenv.config({ path: rootEnv, override: true, debug: false, quiet: true });
|
|
27
|
+
}
|
|
28
|
+
catch { }
|
|
29
|
+
// Note: AI features are lazily imported in thread-service to keep bootstrap light.
|
|
30
|
+
// Note: Startup logging removed to reduce noise in alpha mode
|
|
31
|
+
// Suppress extremely chatty Automerge sync noise that breaks TTY input rendering
|
|
32
|
+
try {
|
|
33
|
+
const noisy = [
|
|
34
|
+
'error receiving message',
|
|
35
|
+
'Attempting to change an outdated document',
|
|
36
|
+
];
|
|
37
|
+
const shouldDrop = (args) => {
|
|
38
|
+
try {
|
|
39
|
+
const s = args.map(a => (typeof a === 'string' ? a : (a && a.stack) || JSON.stringify(a))).join(' ');
|
|
40
|
+
return noisy.some(k => s.includes(k));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const origLog = console.log.bind(console);
|
|
47
|
+
const origErr = console.error.bind(console);
|
|
48
|
+
const origWarn = console.warn.bind(console);
|
|
49
|
+
console.log = (...args) => { if (shouldDrop(args))
|
|
50
|
+
return; origLog(...args); };
|
|
51
|
+
console.error = (...args) => { if (shouldDrop(args))
|
|
52
|
+
return; origErr(...args); };
|
|
53
|
+
console.warn = (...args) => { if (shouldDrop(args))
|
|
54
|
+
return; origWarn(...args); };
|
|
55
|
+
}
|
|
56
|
+
catch { }
|
|
57
|
+
const cli = meow(`
|
|
58
|
+
Usage
|
|
59
|
+
$ mod <command> [options]
|
|
60
|
+
Commands
|
|
61
|
+
create-task <desc>
|
|
62
|
+
set-task-status <id> <status>
|
|
63
|
+
...
|
|
64
|
+
`, {
|
|
65
|
+
importMeta: import.meta,
|
|
66
|
+
flags: {
|
|
67
|
+
preview: {
|
|
68
|
+
type: 'boolean',
|
|
69
|
+
default: false
|
|
70
|
+
},
|
|
71
|
+
verbose: {
|
|
72
|
+
type: 'boolean',
|
|
73
|
+
default: false
|
|
74
|
+
},
|
|
75
|
+
force: {
|
|
76
|
+
type: 'boolean',
|
|
77
|
+
default: false
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
allowUnknownFlags: true
|
|
81
|
+
});
|
|
82
|
+
async function main() {
|
|
83
|
+
const repo = await getRepo();
|
|
84
|
+
const availableCommands = buildCommandRegistry();
|
|
85
|
+
const [cmd, ...args] = cli.input;
|
|
86
|
+
// No user-facing banners or notifications about feature flags
|
|
87
|
+
// Background watch now handled by automatic-file-tracker service
|
|
88
|
+
// Handle command execution
|
|
89
|
+
if (cmd && typeof availableCommands[cmd] === 'function') {
|
|
90
|
+
// Pass both positional args and flags as a combined array
|
|
91
|
+
const allArgs = [...args];
|
|
92
|
+
// Add flags back to args array for backward compatibility
|
|
93
|
+
if (cli.flags.preview)
|
|
94
|
+
allArgs.push('--preview');
|
|
95
|
+
if (cli.flags.verbose)
|
|
96
|
+
allArgs.push('--verbose');
|
|
97
|
+
if (cli.flags.force)
|
|
98
|
+
allArgs.push('--force');
|
|
99
|
+
await availableCommands[cmd](allArgs, repo);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (!cmd) {
|
|
103
|
+
console.log('Mod CLI\n');
|
|
104
|
+
console.log('Available commands:');
|
|
105
|
+
for (const command of Object.keys(availableCommands).sort()) {
|
|
106
|
+
console.log(` mod ${command}`);
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (cmd && !availableCommands[cmd]) {
|
|
111
|
+
console.error(`Unknown command: ${cmd}`);
|
|
112
|
+
console.error('Run "mod" to see available commands.');
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
// Ink UI rendering controlled by features, not exposed to users
|
|
116
|
+
if (isFeatureEnabled(FEATURES.STATUS) || Object.keys(availableCommands).length > 1) {
|
|
117
|
+
const featureFlags = getFeatureFlags(); // Keep for backward compatibility with App component
|
|
118
|
+
render(_jsx(App, { repo: repo, featureFlags: featureFlags }));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
main();
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { readModConfig } from '../services/mod-config.js';
|
|
2
|
+
import { ThreadService } from '@mod/mod-core/services/thread-service';
|
|
3
|
+
export async function agentsRunCommand(args, repo) {
|
|
4
|
+
// Usage: mod agents-run --prompt "..." [--agent dev|planner]
|
|
5
|
+
const promptIndex = args.findIndex(a => a === '--prompt');
|
|
6
|
+
const agentIndex = args.findIndex(a => a === '--agent');
|
|
7
|
+
const prompt = promptIndex !== -1 ? args[promptIndex + 1] : undefined;
|
|
8
|
+
const agentName = agentIndex !== -1 ? (args[agentIndex + 1] || 'dev') : 'dev';
|
|
9
|
+
if (!prompt) {
|
|
10
|
+
console.error('Usage: mod agents-run --prompt "..." [--agent dev|planner]');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const config = readModConfig();
|
|
14
|
+
if (!config?.workspaceId) {
|
|
15
|
+
console.error('No active workspace found in .mod/config.json');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const wsHandle = await repo.find(config.workspaceId);
|
|
19
|
+
const workspace = await wsHandle.doc();
|
|
20
|
+
if (!workspace) {
|
|
21
|
+
console.error('Workspace not found');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const threadService = new ThreadService(repo);
|
|
25
|
+
const threadId = workspace.activeThreadId || (await (async () => {
|
|
26
|
+
const t = await threadService.createThreadWithBranch(workspace.id, workspace.branchesDocId, workspace.activeBranchId, 'CLI Thread');
|
|
27
|
+
return t.id;
|
|
28
|
+
})());
|
|
29
|
+
// Lazy import agents (shimmed types)
|
|
30
|
+
// Agent packages not yet available - placeholder implementation
|
|
31
|
+
console.error('Agent packages (@mod/mod-agents) are not yet implemented');
|
|
32
|
+
console.log(`Requested agent: ${agentName}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
/*
|
|
35
|
+
let agent: any;
|
|
36
|
+
if (agentName === 'planner' || agentName === 'plan') {
|
|
37
|
+
const mod = await import('@mod/mod-agents/planner');
|
|
38
|
+
agent = (mod as any).plannerAgent || (mod as any).default;
|
|
39
|
+
} else {
|
|
40
|
+
const mod = await import('@mod/mod-agents/dev-agent');
|
|
41
|
+
agent = (mod as any).devAgent || (mod as any).default;
|
|
42
|
+
}
|
|
43
|
+
*/
|
|
44
|
+
/*
|
|
45
|
+
const tools: Record<string, any> = {};
|
|
46
|
+
const apiKey = process.env.OPENAI_API_KEY || process.env.OPENAI || process.env.ANTHROPIC_API_KEY || '';
|
|
47
|
+
try {
|
|
48
|
+
for await (const chunk of chatWithAgentCli({
|
|
49
|
+
repo: repo as any,
|
|
50
|
+
threadId: threadId as any,
|
|
51
|
+
userMessage: prompt,
|
|
52
|
+
workspace,
|
|
53
|
+
agent,
|
|
54
|
+
tools,
|
|
55
|
+
apiKey,
|
|
56
|
+
})) {
|
|
57
|
+
if (chunk.type === 'content') {
|
|
58
|
+
process.stdout.write(chunk.content);
|
|
59
|
+
} else if (chunk.type === 'tool-call') {
|
|
60
|
+
log('[tool-call]', (chunk as any).toolName || '');
|
|
61
|
+
} else if (chunk.type === 'tool-result') {
|
|
62
|
+
log('[tool-result]', (chunk as any).output?.toolName || '');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch (e: any) {
|
|
66
|
+
console.error('Agent run failed:', e?.message || e);
|
|
67
|
+
} finally {
|
|
68
|
+
setTimeout(() => process.exit(0), 500);
|
|
69
|
+
}
|
|
70
|
+
*/
|
|
71
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// glassware[type=implementation, id=cli-auth-command, requirements=req-cli-auth-ux-2,req-cli-auth-ux-3,req-cli-auth-ux-4,req-cli-auth-ux-5,req-cli-auth-ux-6,req-cli-auth-app-1,req-cli-auth-app-5]
|
|
2
|
+
import { readConfig, writeConfig } from '../lib/storage.js';
|
|
3
|
+
import { openBrowser } from '../lib/browser.js';
|
|
4
|
+
import { startAuthServer } from '../lib/auth-server.js';
|
|
5
|
+
// Auth server base URL - configurable via environment variable
|
|
6
|
+
const AUTH_BASE_URL = process.env.MOD_AUTH_URL || 'https://auth.mod.app';
|
|
7
|
+
const API_BASE_URL = process.env.MOD_API_URL || 'https://api.mod.app';
|
|
8
|
+
export async function authCommand(args, repo) {
|
|
9
|
+
const [subcommand] = args;
|
|
10
|
+
switch (subcommand) {
|
|
11
|
+
case 'login':
|
|
12
|
+
await handleLogin();
|
|
13
|
+
break;
|
|
14
|
+
case 'logout':
|
|
15
|
+
await handleLogout();
|
|
16
|
+
break;
|
|
17
|
+
case 'status':
|
|
18
|
+
await handleStatus();
|
|
19
|
+
break;
|
|
20
|
+
default:
|
|
21
|
+
if (!subcommand) {
|
|
22
|
+
await handleStatus();
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.error('Usage: mod auth <login|logout|status>');
|
|
26
|
+
console.error('Available commands:');
|
|
27
|
+
console.error(' login Sign in with Google OAuth');
|
|
28
|
+
console.error(' logout Sign out and clear credentials');
|
|
29
|
+
console.error(' status Show current authentication state');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
async function handleLogin() {
|
|
36
|
+
// Check if already authenticated
|
|
37
|
+
const config = readConfig();
|
|
38
|
+
if (config.auth) {
|
|
39
|
+
console.log(`Already signed in as ${config.auth.email}`);
|
|
40
|
+
console.log('');
|
|
41
|
+
console.log('Run `mod auth logout` to sign out first.');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Start localhost server to receive callback
|
|
45
|
+
const { port, result, close } = await startAuthServer();
|
|
46
|
+
// Build auth URL with callback port
|
|
47
|
+
const authUrl = `${AUTH_BASE_URL}/cli?port=${port}`;
|
|
48
|
+
console.log('Opening browser to sign in...');
|
|
49
|
+
// Try to open browser
|
|
50
|
+
const opened = await openBrowser(authUrl);
|
|
51
|
+
if (!opened) {
|
|
52
|
+
console.log('');
|
|
53
|
+
console.log('Could not open browser. Please visit:');
|
|
54
|
+
console.log(authUrl);
|
|
55
|
+
console.log('');
|
|
56
|
+
}
|
|
57
|
+
console.log('Waiting for authentication...');
|
|
58
|
+
try {
|
|
59
|
+
// Wait for auth callback
|
|
60
|
+
const authResult = await result;
|
|
61
|
+
// Validate token with server (optional - server may not be available)
|
|
62
|
+
let userDocId;
|
|
63
|
+
try {
|
|
64
|
+
userDocId = await validateAndGetUserDocId(authResult.googleIdToken);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
// If validation fails, continue without userDocId
|
|
68
|
+
// It will be set on first sync
|
|
69
|
+
}
|
|
70
|
+
// Store credentials in config
|
|
71
|
+
const updatedConfig = readConfig();
|
|
72
|
+
updatedConfig.auth = {
|
|
73
|
+
googleIdToken: authResult.googleIdToken,
|
|
74
|
+
googleId: authResult.googleId,
|
|
75
|
+
email: authResult.email,
|
|
76
|
+
name: authResult.name,
|
|
77
|
+
userDocId: userDocId || '',
|
|
78
|
+
};
|
|
79
|
+
writeConfig(updatedConfig);
|
|
80
|
+
console.log('');
|
|
81
|
+
console.log(`Signed in as ${authResult.email}`);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof Error) {
|
|
85
|
+
if (error.message.includes('timed out')) {
|
|
86
|
+
console.error('');
|
|
87
|
+
console.error('Authentication timed out. Please try again.');
|
|
88
|
+
}
|
|
89
|
+
else if (error.message.includes('cancelled')) {
|
|
90
|
+
console.error('');
|
|
91
|
+
console.error('Authentication cancelled.');
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
console.error('');
|
|
95
|
+
console.error(`Authentication failed: ${error.message}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function handleLogout() {
|
|
102
|
+
const config = readConfig();
|
|
103
|
+
if (!config.auth) {
|
|
104
|
+
console.log('Not signed in.');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const email = config.auth.email;
|
|
108
|
+
// Clear auth from config
|
|
109
|
+
config.auth = null;
|
|
110
|
+
writeConfig(config);
|
|
111
|
+
console.log('Signed out successfully');
|
|
112
|
+
console.log(`Removed credentials for ${email}`);
|
|
113
|
+
}
|
|
114
|
+
async function handleStatus() {
|
|
115
|
+
const config = readConfig();
|
|
116
|
+
if (!config.auth) {
|
|
117
|
+
console.log('Not signed in.');
|
|
118
|
+
console.log('');
|
|
119
|
+
console.log('Sign in to sync with collaborators: mod auth login');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
console.log(`Signed in as ${config.auth.email}`);
|
|
123
|
+
if (config.auth.name && config.auth.name !== config.auth.email) {
|
|
124
|
+
console.log(`Name: ${config.auth.name}`);
|
|
125
|
+
}
|
|
126
|
+
console.log('Token expires: never');
|
|
127
|
+
// Check if token is still valid (optional)
|
|
128
|
+
try {
|
|
129
|
+
await validateAndGetUserDocId(config.auth.googleIdToken);
|
|
130
|
+
console.log('Token status: valid');
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.log('Token status: could not verify (server unavailable)');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Validate token with server and get user document ID.
|
|
138
|
+
* Returns existing userDocId for returning users, or undefined for new users.
|
|
139
|
+
*/
|
|
140
|
+
async function validateAndGetUserDocId(token) {
|
|
141
|
+
const response = await fetch(`${API_BASE_URL}/auth/me`, {
|
|
142
|
+
headers: {
|
|
143
|
+
Authorization: `Bearer ${token}`,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
throw new Error('Token validation failed');
|
|
148
|
+
}
|
|
149
|
+
const data = (await response.json());
|
|
150
|
+
return data.userDocId;
|
|
151
|
+
}
|