gemkit-cli 0.2.3 → 0.3.1
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 +141 -7
- package/dist/commands/agent/index.d.ts +9 -0
- package/dist/commands/agent/index.js +1329 -0
- package/dist/commands/cache/index.d.ts +5 -0
- package/dist/commands/cache/index.js +43 -0
- package/dist/commands/catalog/index.d.ts +2 -0
- package/dist/commands/catalog/index.js +57 -0
- package/dist/commands/config/index.d.ts +7 -0
- package/dist/commands/config/index.js +122 -0
- package/dist/commands/convert/index.d.ts +8 -0
- package/dist/commands/convert/index.js +391 -0
- package/dist/commands/doctor/index.d.ts +2 -0
- package/dist/commands/doctor/index.js +243 -0
- package/dist/commands/extension/index.d.ts +5 -0
- package/dist/commands/extension/index.js +52 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.js +37 -0
- package/dist/commands/init/index.d.ts +6 -0
- package/dist/commands/init/index.js +345 -0
- package/dist/commands/new/index.d.ts +5 -0
- package/dist/commands/new/index.js +49 -0
- package/dist/commands/office/index.d.ts +5 -0
- package/dist/commands/office/index.js +283 -0
- package/dist/commands/paste/index.d.ts +10 -0
- package/dist/commands/paste/index.js +533 -0
- package/dist/commands/plan/index.d.ts +8 -0
- package/dist/commands/plan/index.js +247 -0
- package/dist/commands/session/index.d.ts +8 -0
- package/dist/commands/session/index.js +289 -0
- package/dist/commands/tokens/index.d.ts +6 -0
- package/dist/commands/tokens/index.js +148 -0
- package/dist/commands/update/index.d.ts +26 -0
- package/dist/commands/update/index.js +199 -0
- package/dist/commands/versions/index.d.ts +5 -0
- package/dist/commands/versions/index.js +39 -0
- package/dist/domains/agent/index.d.ts +8 -0
- package/dist/domains/agent/index.js +8 -0
- package/dist/domains/agent/mappings.d.ts +32 -0
- package/dist/domains/agent/mappings.js +164 -0
- package/dist/domains/agent/profile.d.ts +26 -0
- package/dist/domains/agent/profile.js +225 -0
- package/dist/domains/agent/pty-context.d.ts +11 -0
- package/dist/domains/agent/pty-context.js +83 -0
- package/dist/domains/agent/pty-providers.d.ts +18 -0
- package/dist/domains/agent/pty-providers.js +66 -0
- package/dist/domains/agent/pty-session.d.ts +33 -0
- package/dist/domains/agent/pty-session.js +82 -0
- package/dist/domains/agent/pty-types.d.ts +127 -0
- package/dist/domains/agent/pty-types.js +4 -0
- package/dist/domains/agent/search.d.ts +45 -0
- package/dist/domains/agent/search.js +614 -0
- package/dist/domains/agent/types.d.ts +78 -0
- package/dist/domains/agent/types.js +5 -0
- package/dist/domains/agent-office/documents-scanner.d.ts +9 -0
- package/dist/domains/agent-office/documents-scanner.js +143 -0
- package/dist/domains/agent-office/event-emitter.d.ts +43 -0
- package/dist/domains/agent-office/event-emitter.js +86 -0
- package/dist/domains/agent-office/file-watcher.d.ts +40 -0
- package/dist/domains/agent-office/file-watcher.js +173 -0
- package/dist/domains/agent-office/icons.d.ts +11 -0
- package/dist/domains/agent-office/icons.js +36 -0
- package/dist/domains/agent-office/index.d.ts +12 -0
- package/dist/domains/agent-office/index.js +20 -0
- package/dist/domains/agent-office/renderer/web/assets.d.ts +11 -0
- package/dist/domains/agent-office/renderer/web/assets.js +3419 -0
- package/dist/domains/agent-office/renderer/web/server.d.ts +42 -0
- package/dist/domains/agent-office/renderer/web/server.js +228 -0
- package/dist/domains/agent-office/renderer/web.d.ts +30 -0
- package/dist/domains/agent-office/renderer/web.js +111 -0
- package/dist/domains/agent-office/session-bridge.d.ts +23 -0
- package/dist/domains/agent-office/session-bridge.js +171 -0
- package/dist/domains/agent-office/state-machine.d.ts +5 -0
- package/dist/domains/agent-office/state-machine.js +82 -0
- package/dist/domains/agent-office/types.d.ts +91 -0
- package/dist/domains/agent-office/types.js +4 -0
- package/dist/domains/cache/index.d.ts +1 -0
- package/dist/domains/cache/index.js +1 -0
- package/dist/domains/cache/manager.d.ts +22 -0
- package/dist/domains/cache/manager.js +84 -0
- package/dist/domains/config/index.d.ts +5 -0
- package/dist/domains/config/index.js +5 -0
- package/dist/domains/config/manager.d.ts +24 -0
- package/dist/domains/config/manager.js +85 -0
- package/dist/domains/config/schema.d.ts +17 -0
- package/dist/domains/config/schema.js +96 -0
- package/dist/domains/convert/converter.d.ts +78 -0
- package/dist/domains/convert/converter.js +471 -0
- package/dist/domains/convert/index.d.ts +5 -0
- package/dist/domains/convert/index.js +5 -0
- package/dist/domains/convert/types.d.ts +88 -0
- package/dist/domains/convert/types.js +18 -0
- package/dist/domains/github/download.d.ts +12 -0
- package/dist/domains/github/download.js +51 -0
- package/dist/domains/github/index.d.ts +2 -0
- package/dist/domains/github/index.js +2 -0
- package/dist/domains/github/releases.d.ts +16 -0
- package/dist/domains/github/releases.js +68 -0
- package/dist/domains/installation/conflict.d.ts +13 -0
- package/dist/domains/installation/conflict.js +38 -0
- package/dist/domains/installation/file-sync.d.ts +16 -0
- package/dist/domains/installation/file-sync.js +77 -0
- package/dist/domains/installation/index.d.ts +3 -0
- package/dist/domains/installation/index.js +3 -0
- package/dist/domains/installation/metadata.d.ts +20 -0
- package/dist/domains/installation/metadata.js +52 -0
- package/dist/domains/plan/index.d.ts +2 -0
- package/dist/domains/plan/index.js +2 -0
- package/dist/domains/plan/resolver.d.ts +24 -0
- package/dist/domains/plan/resolver.js +164 -0
- package/dist/domains/plan/types.d.ts +13 -0
- package/dist/domains/plan/types.js +4 -0
- package/dist/domains/session/env.d.ts +51 -0
- package/dist/domains/session/env.js +118 -0
- package/dist/domains/session/index.d.ts +8 -0
- package/dist/domains/session/index.js +8 -0
- package/dist/domains/session/manager.d.ts +56 -0
- package/dist/domains/session/manager.js +205 -0
- package/dist/domains/session/paths.d.ts +6 -0
- package/dist/domains/session/paths.js +6 -0
- package/dist/domains/session/types.d.ts +121 -0
- package/dist/domains/session/types.js +5 -0
- package/dist/domains/session/writer.d.ts +82 -0
- package/dist/domains/session/writer.js +431 -0
- package/dist/domains/tokens/index.d.ts +5 -0
- package/dist/domains/tokens/index.js +5 -0
- package/dist/domains/tokens/pricing.d.ts +38 -0
- package/dist/domains/tokens/pricing.js +129 -0
- package/dist/domains/tokens/scanner.d.ts +42 -0
- package/dist/domains/tokens/scanner.js +168 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +90 -59
- package/dist/services/aipty.d.ts +76 -0
- package/dist/services/aipty.js +276 -0
- package/dist/services/archive.d.ts +22 -0
- package/dist/services/archive.js +53 -0
- package/dist/services/auto-update.d.ts +26 -0
- package/dist/services/auto-update.js +117 -0
- package/dist/services/hash.d.ts +36 -0
- package/dist/services/hash.js +63 -0
- package/dist/services/logger.d.ts +28 -0
- package/dist/services/logger.js +102 -0
- package/dist/services/music.d.ts +67 -0
- package/dist/services/music.js +290 -0
- package/dist/services/npm.d.ts +22 -0
- package/dist/services/npm.js +65 -0
- package/dist/services/pty-client.d.ts +66 -0
- package/dist/services/pty-client.js +154 -0
- package/dist/services/pty-server.d.ts +102 -0
- package/dist/services/pty-server.js +613 -0
- package/dist/types/index.d.ts +155 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/colors.d.ts +43 -0
- package/dist/utils/colors.js +98 -0
- package/dist/utils/errors.d.ts +24 -0
- package/dist/utils/errors.js +56 -0
- package/dist/utils/paths.d.ts +46 -0
- package/dist/utils/paths.js +89 -0
- package/dist/utils/platform.d.ts +11 -0
- package/dist/utils/platform.js +31 -0
- package/package.json +55 -54
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub releases API - Public repos only, no auth
|
|
3
|
+
*/
|
|
4
|
+
import { loadConfig } from '../config/manager.js';
|
|
5
|
+
import { getCache, setCache } from '../cache/manager.js';
|
|
6
|
+
import { GitHubError } from '../../utils/errors.js';
|
|
7
|
+
const CACHE_KEY = 'github-releases';
|
|
8
|
+
const CACHE_TTL = 300; // 5 minutes
|
|
9
|
+
/**
|
|
10
|
+
* Fetch releases from GitHub
|
|
11
|
+
*/
|
|
12
|
+
export async function fetchReleases(limit = 10) {
|
|
13
|
+
// Check cache first
|
|
14
|
+
const cached = getCache(CACHE_KEY);
|
|
15
|
+
if (cached) {
|
|
16
|
+
return cached.slice(0, limit);
|
|
17
|
+
}
|
|
18
|
+
const config = loadConfig();
|
|
19
|
+
const { repo, apiUrl } = config.github;
|
|
20
|
+
const url = `${apiUrl}/repos/${repo}/releases?per_page=${limit}`;
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch(url, {
|
|
23
|
+
headers: {
|
|
24
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
25
|
+
'User-Agent': 'gemkit-cli',
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new GitHubError(`Failed to fetch releases: ${response.status}`);
|
|
30
|
+
}
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
const releases = data.map(r => ({
|
|
33
|
+
version: r.tag_name.replace(/^v/, ''),
|
|
34
|
+
tag: r.tag_name,
|
|
35
|
+
publishedAt: r.published_at,
|
|
36
|
+
prerelease: r.prerelease,
|
|
37
|
+
assets: r.assets.map(a => ({
|
|
38
|
+
name: a.name,
|
|
39
|
+
url: a.url,
|
|
40
|
+
size: a.size,
|
|
41
|
+
downloadUrl: a.browser_download_url,
|
|
42
|
+
})),
|
|
43
|
+
}));
|
|
44
|
+
// Cache results
|
|
45
|
+
setCache(CACHE_KEY, releases, CACHE_TTL);
|
|
46
|
+
return releases;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error instanceof GitHubError) {
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
throw new GitHubError(`Failed to fetch releases: ${error}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get latest release
|
|
57
|
+
*/
|
|
58
|
+
export async function getLatestRelease() {
|
|
59
|
+
const releases = await fetchReleases(1);
|
|
60
|
+
return releases.length > 0 ? releases[0] : null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get release by version
|
|
64
|
+
*/
|
|
65
|
+
export async function getReleaseByVersion(version) {
|
|
66
|
+
const releases = await fetchReleases(50);
|
|
67
|
+
return releases.find(r => r.version === version || r.tag === version) || null;
|
|
68
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conflict detection for file updates
|
|
3
|
+
*/
|
|
4
|
+
export interface ConflictInfo {
|
|
5
|
+
path: string;
|
|
6
|
+
type: 'modified' | 'deleted' | 'new';
|
|
7
|
+
localHash?: string;
|
|
8
|
+
originalHash?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Detect conflicts between local changes and update
|
|
12
|
+
*/
|
|
13
|
+
export declare function detectConflicts(projectDir?: string): ConflictInfo[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conflict detection for file updates
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { hashFile } from '../../services/hash.js';
|
|
6
|
+
import { loadMetadata } from './metadata.js';
|
|
7
|
+
/**
|
|
8
|
+
* Detect conflicts between local changes and update
|
|
9
|
+
*/
|
|
10
|
+
export function detectConflicts(projectDir) {
|
|
11
|
+
const conflicts = [];
|
|
12
|
+
const metadata = loadMetadata(projectDir);
|
|
13
|
+
if (!metadata) {
|
|
14
|
+
return conflicts;
|
|
15
|
+
}
|
|
16
|
+
// Check customized files
|
|
17
|
+
for (const custom of metadata.customizedFiles) {
|
|
18
|
+
if (!existsSync(custom.path)) {
|
|
19
|
+
conflicts.push({
|
|
20
|
+
path: custom.path,
|
|
21
|
+
type: 'deleted',
|
|
22
|
+
originalHash: custom.hash,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
const currentHash = hashFile(custom.path);
|
|
27
|
+
if (currentHash !== custom.hash) {
|
|
28
|
+
conflicts.push({
|
|
29
|
+
path: custom.path,
|
|
30
|
+
type: 'modified',
|
|
31
|
+
localHash: currentHash || undefined,
|
|
32
|
+
originalHash: custom.hash,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return conflicts;
|
|
38
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File synchronization for installation/update
|
|
3
|
+
*/
|
|
4
|
+
export interface SyncResult {
|
|
5
|
+
added: string[];
|
|
6
|
+
updated: string[];
|
|
7
|
+
skipped: string[];
|
|
8
|
+
errors: string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Sync files from source to destination
|
|
12
|
+
*/
|
|
13
|
+
export declare function syncFiles(sourceDir: string, destDir: string, options?: {
|
|
14
|
+
force?: boolean;
|
|
15
|
+
excludePatterns?: string[];
|
|
16
|
+
}): SyncResult;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File synchronization for installation/update
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, mkdirSync, readdirSync, copyFileSync } from 'fs';
|
|
5
|
+
import { join, dirname, relative } from 'path';
|
|
6
|
+
import { hashFile } from '../../services/hash.js';
|
|
7
|
+
/**
|
|
8
|
+
* Sync files from source to destination
|
|
9
|
+
*/
|
|
10
|
+
export function syncFiles(sourceDir, destDir, options = {}) {
|
|
11
|
+
const result = {
|
|
12
|
+
added: [],
|
|
13
|
+
updated: [],
|
|
14
|
+
skipped: [],
|
|
15
|
+
errors: [],
|
|
16
|
+
};
|
|
17
|
+
if (!existsSync(sourceDir)) {
|
|
18
|
+
result.errors.push(`Source directory not found: ${sourceDir}`);
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
const files = getAllFiles(sourceDir);
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
const relativePath = relative(sourceDir, file);
|
|
24
|
+
const destPath = join(destDir, relativePath);
|
|
25
|
+
// Check exclude patterns
|
|
26
|
+
if (options.excludePatterns?.some(p => relativePath.includes(p))) {
|
|
27
|
+
result.skipped.push(relativePath);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
// Ensure destination directory exists
|
|
32
|
+
const destDirPath = dirname(destPath);
|
|
33
|
+
if (!existsSync(destDirPath)) {
|
|
34
|
+
mkdirSync(destDirPath, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
if (!existsSync(destPath)) {
|
|
37
|
+
// New file
|
|
38
|
+
copyFileSync(file, destPath);
|
|
39
|
+
result.added.push(relativePath);
|
|
40
|
+
}
|
|
41
|
+
else if (options.force) {
|
|
42
|
+
// Force update
|
|
43
|
+
copyFileSync(file, destPath);
|
|
44
|
+
result.updated.push(relativePath);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Check if file changed
|
|
48
|
+
const sourceHash = hashFile(file);
|
|
49
|
+
const destHash = hashFile(destPath);
|
|
50
|
+
if (sourceHash !== destHash) {
|
|
51
|
+
result.skipped.push(relativePath);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
result.errors.push(`Failed to sync ${relativePath}: ${error}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get all files in directory recursively
|
|
63
|
+
*/
|
|
64
|
+
function getAllFiles(dir) {
|
|
65
|
+
const files = [];
|
|
66
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
const fullPath = join(dir, entry.name);
|
|
69
|
+
if (entry.isDirectory()) {
|
|
70
|
+
files.push(...getAllFiles(fullPath));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
files.push(fullPath);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return files;
|
|
77
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installation metadata management
|
|
3
|
+
*/
|
|
4
|
+
import type { GemKitMetadata } from '../../types/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Load installation metadata
|
|
7
|
+
*/
|
|
8
|
+
export declare function loadMetadata(projectDir?: string): GemKitMetadata | null;
|
|
9
|
+
/**
|
|
10
|
+
* Save installation metadata
|
|
11
|
+
*/
|
|
12
|
+
export declare function saveMetadata(metadata: GemKitMetadata, projectDir?: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* Create initial metadata
|
|
15
|
+
*/
|
|
16
|
+
export declare function createMetadata(version: string, scope: 'local' | 'global', installedFiles: string[]): GemKitMetadata;
|
|
17
|
+
/**
|
|
18
|
+
* Check if GemKit is installed
|
|
19
|
+
*/
|
|
20
|
+
export declare function isInstalled(projectDir?: string): boolean;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installation metadata management
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
import { getLocalMetadataPath } from '../../utils/paths.js';
|
|
7
|
+
/**
|
|
8
|
+
* Load installation metadata
|
|
9
|
+
*/
|
|
10
|
+
export function loadMetadata(projectDir) {
|
|
11
|
+
const metadataPath = getLocalMetadataPath(projectDir);
|
|
12
|
+
if (!existsSync(metadataPath)) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const content = readFileSync(metadataPath, 'utf-8');
|
|
17
|
+
return JSON.parse(content);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Save installation metadata
|
|
25
|
+
*/
|
|
26
|
+
export function saveMetadata(metadata, projectDir) {
|
|
27
|
+
const metadataPath = getLocalMetadataPath(projectDir);
|
|
28
|
+
const dir = dirname(metadataPath);
|
|
29
|
+
if (!existsSync(dir)) {
|
|
30
|
+
mkdirSync(dir, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create initial metadata
|
|
36
|
+
*/
|
|
37
|
+
export function createMetadata(version, scope, installedFiles) {
|
|
38
|
+
return {
|
|
39
|
+
name: 'gemkit',
|
|
40
|
+
version,
|
|
41
|
+
installedAt: new Date().toISOString(),
|
|
42
|
+
scope,
|
|
43
|
+
installedFiles,
|
|
44
|
+
customizedFiles: [],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if GemKit is installed
|
|
49
|
+
*/
|
|
50
|
+
export function isInstalled(projectDir) {
|
|
51
|
+
return loadMetadata(projectDir) !== null;
|
|
52
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan resolver utilities
|
|
3
|
+
* Replaces: gk-set-active-plan.cjs (setActivePlan function)
|
|
4
|
+
*/
|
|
5
|
+
import { Plan } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* List all plans in project
|
|
8
|
+
*/
|
|
9
|
+
export declare function listPlans(projectDir?: string): Plan[];
|
|
10
|
+
/**
|
|
11
|
+
* Create a new plan
|
|
12
|
+
*/
|
|
13
|
+
export declare function createPlan(name: string, projectDir?: string): Plan;
|
|
14
|
+
/**
|
|
15
|
+
* Set active plan in session file AND .env
|
|
16
|
+
* Matches gk-set-active-plan.cjs behavior:
|
|
17
|
+
* 1. Updates session.activePlan in ~/.gemkit/projects/{projectDir}/gk-session-{gkSessionId}.json
|
|
18
|
+
* 2. Updates ACTIVE_PLAN in .gemini/.env
|
|
19
|
+
*/
|
|
20
|
+
export declare function setActivePlan(name: string, projectDir?: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Get plan info
|
|
23
|
+
*/
|
|
24
|
+
export declare function getPlanInfo(name: string, projectDir?: string): Plan | null;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan resolver utilities
|
|
3
|
+
* Replaces: gk-set-active-plan.cjs (setActivePlan function)
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readdirSync, mkdirSync, writeFileSync, statSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { getPlansDir, getLocalEnvPath } from '../../utils/paths.js';
|
|
8
|
+
import { getActivePlan, getActiveGkSessionId, getProjectDir, readEnv } from '../session/env.js';
|
|
9
|
+
import { getSession } from '../session/manager.js';
|
|
10
|
+
import { setActivePlan as sessionSetActivePlan } from '../session/writer.js';
|
|
11
|
+
/**
|
|
12
|
+
* List all plans in project
|
|
13
|
+
*/
|
|
14
|
+
export function listPlans(projectDir) {
|
|
15
|
+
const plansDir = getPlansDir(projectDir);
|
|
16
|
+
if (!existsSync(plansDir)) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
const activePlan = getActivePlan();
|
|
20
|
+
const dirs = readdirSync(plansDir, { withFileTypes: true })
|
|
21
|
+
.filter(d => d.isDirectory())
|
|
22
|
+
.map(d => d.name);
|
|
23
|
+
return dirs.map(name => {
|
|
24
|
+
const path = join(plansDir, name);
|
|
25
|
+
const planFile = join(path, 'plan.md');
|
|
26
|
+
let createdAt = '';
|
|
27
|
+
// Try to get creation date from plan.md or folder name
|
|
28
|
+
if (existsSync(planFile)) {
|
|
29
|
+
const stat = statSync(planFile);
|
|
30
|
+
createdAt = stat.birthtime.toISOString();
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
name,
|
|
34
|
+
path,
|
|
35
|
+
createdAt,
|
|
36
|
+
isActive: name === activePlan,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create a new plan
|
|
42
|
+
*/
|
|
43
|
+
export function createPlan(name, projectDir) {
|
|
44
|
+
const plansDir = getPlansDir(projectDir);
|
|
45
|
+
const planPath = join(plansDir, name);
|
|
46
|
+
if (!existsSync(plansDir)) {
|
|
47
|
+
mkdirSync(plansDir, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
if (existsSync(planPath)) {
|
|
50
|
+
throw new Error(`Plan already exists: ${name}`);
|
|
51
|
+
}
|
|
52
|
+
// Create plan structure
|
|
53
|
+
mkdirSync(planPath, { recursive: true });
|
|
54
|
+
mkdirSync(join(planPath, 'research'), { recursive: true });
|
|
55
|
+
mkdirSync(join(planPath, 'artifacts'), { recursive: true });
|
|
56
|
+
// Create plan.md template
|
|
57
|
+
const template = `# ${name}
|
|
58
|
+
|
|
59
|
+
## Overview
|
|
60
|
+
|
|
61
|
+
[Describe the plan objectives here]
|
|
62
|
+
|
|
63
|
+
## Tasks
|
|
64
|
+
|
|
65
|
+
- [ ] Task 1
|
|
66
|
+
- [ ] Task 2
|
|
67
|
+
- [ ] Task 3
|
|
68
|
+
|
|
69
|
+
## Notes
|
|
70
|
+
|
|
71
|
+
`;
|
|
72
|
+
writeFileSync(join(planPath, 'plan.md'), template);
|
|
73
|
+
return {
|
|
74
|
+
name,
|
|
75
|
+
path: planPath,
|
|
76
|
+
createdAt: new Date().toISOString(),
|
|
77
|
+
isActive: false,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Set active plan in session file AND .env
|
|
82
|
+
* Matches gk-set-active-plan.cjs behavior:
|
|
83
|
+
* 1. Updates session.activePlan in ~/.gemkit/projects/{projectDir}/gk-session-{gkSessionId}.json
|
|
84
|
+
* 2. Updates ACTIVE_PLAN in .gemini/.env
|
|
85
|
+
*/
|
|
86
|
+
export function setActivePlan(name, projectDir) {
|
|
87
|
+
// Get active session ID and project dir from .env
|
|
88
|
+
const gkSessionId = getActiveGkSessionId();
|
|
89
|
+
if (!gkSessionId) {
|
|
90
|
+
// No active session - just update env directly
|
|
91
|
+
const envPath = getLocalEnvPath(projectDir);
|
|
92
|
+
const env = readEnv();
|
|
93
|
+
// Build new env content with ACTIVE_PLAN
|
|
94
|
+
const content = [
|
|
95
|
+
'# Auto-generated by gemkit-cli',
|
|
96
|
+
`# Updated at: ${new Date().toISOString()}`,
|
|
97
|
+
'',
|
|
98
|
+
'# GEMKIT IDs',
|
|
99
|
+
`ACTIVE_GK_SESSION_ID=${env.ACTIVE_GK_SESSION_ID || ''}`,
|
|
100
|
+
`GK_PROJECT_HASH=${env.GK_PROJECT_HASH || ''}`,
|
|
101
|
+
`PROJECT_DIR=${env.PROJECT_DIR || ''}`,
|
|
102
|
+
'',
|
|
103
|
+
'# GEMINI IDs (mapped)',
|
|
104
|
+
`ACTIVE_GEMINI_SESSION_ID=${env.ACTIVE_GEMINI_SESSION_ID || ''}`,
|
|
105
|
+
`GEMINI_PROJECT_HASH=${env.GEMINI_PROJECT_HASH || ''}`,
|
|
106
|
+
'',
|
|
107
|
+
'# PLAN INFO',
|
|
108
|
+
`ACTIVE_PLAN=${name}`,
|
|
109
|
+
`SUGGESTED_PLAN=${env.SUGGESTED_PLAN || ''}`,
|
|
110
|
+
`PLAN_DATE_FORMAT=${env.PLAN_DATE_FORMAT || ''}`,
|
|
111
|
+
''
|
|
112
|
+
].join('\n');
|
|
113
|
+
writeFileSync(envPath, content);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
const projDir = getProjectDir(projectDir);
|
|
117
|
+
// Verify session exists
|
|
118
|
+
const session = getSession(projDir, gkSessionId);
|
|
119
|
+
if (!session) {
|
|
120
|
+
// Session not found - just update env
|
|
121
|
+
const envPath = getLocalEnvPath(projectDir);
|
|
122
|
+
const env = readEnv();
|
|
123
|
+
const content = [
|
|
124
|
+
'# Auto-generated by gemkit-cli',
|
|
125
|
+
`# Updated at: ${new Date().toISOString()}`,
|
|
126
|
+
'',
|
|
127
|
+
'# GEMKIT IDs',
|
|
128
|
+
`ACTIVE_GK_SESSION_ID=${env.ACTIVE_GK_SESSION_ID || ''}`,
|
|
129
|
+
`GK_PROJECT_HASH=${env.GK_PROJECT_HASH || ''}`,
|
|
130
|
+
`PROJECT_DIR=${env.PROJECT_DIR || ''}`,
|
|
131
|
+
'',
|
|
132
|
+
'# GEMINI IDs (mapped)',
|
|
133
|
+
`ACTIVE_GEMINI_SESSION_ID=${env.ACTIVE_GEMINI_SESSION_ID || ''}`,
|
|
134
|
+
`GEMINI_PROJECT_HASH=${env.GEMINI_PROJECT_HASH || ''}`,
|
|
135
|
+
'',
|
|
136
|
+
'# PLAN INFO',
|
|
137
|
+
`ACTIVE_PLAN=${name}`,
|
|
138
|
+
`SUGGESTED_PLAN=${env.SUGGESTED_PLAN || ''}`,
|
|
139
|
+
`PLAN_DATE_FORMAT=${env.PLAN_DATE_FORMAT || ''}`,
|
|
140
|
+
''
|
|
141
|
+
].join('\n');
|
|
142
|
+
writeFileSync(envPath, content);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
// Update session file AND env file using session writer
|
|
146
|
+
return sessionSetActivePlan(projDir, gkSessionId, name);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get plan info
|
|
150
|
+
*/
|
|
151
|
+
export function getPlanInfo(name, projectDir) {
|
|
152
|
+
const plansDir = getPlansDir(projectDir);
|
|
153
|
+
const planPath = join(plansDir, name);
|
|
154
|
+
if (!existsSync(planPath)) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const activePlan = getActivePlan();
|
|
158
|
+
return {
|
|
159
|
+
name,
|
|
160
|
+
path: planPath,
|
|
161
|
+
createdAt: '',
|
|
162
|
+
isActive: name === activePlan,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment variable utilities for session management
|
|
3
|
+
* Aligned with gk-session-manager.cjs readEnv()
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Environment data structure matching gk-session-manager.cjs
|
|
7
|
+
*/
|
|
8
|
+
export interface GkEnvData {
|
|
9
|
+
ACTIVE_GK_SESSION_ID: string;
|
|
10
|
+
GK_PROJECT_HASH: string;
|
|
11
|
+
PROJECT_DIR: string;
|
|
12
|
+
ACTIVE_GEMINI_SESSION_ID: string;
|
|
13
|
+
GEMINI_PROJECT_HASH: string;
|
|
14
|
+
GEMINI_PARENT_ID: string;
|
|
15
|
+
ACTIVE_PLAN: string;
|
|
16
|
+
SUGGESTED_PLAN: string;
|
|
17
|
+
PLAN_DATE_FORMAT: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Read environment variables from .gemini/.env
|
|
21
|
+
* Matches gk-session-manager.cjs readEnv()
|
|
22
|
+
*/
|
|
23
|
+
export declare function readEnv(projectPath?: string): GkEnvData;
|
|
24
|
+
/**
|
|
25
|
+
* Get active GK session ID from .gemini/.env
|
|
26
|
+
*/
|
|
27
|
+
export declare function getActiveGkSessionId(projectPath?: string): string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Get active Gemini session ID from .gemini/.env
|
|
30
|
+
*/
|
|
31
|
+
export declare function getActiveGeminiSessionId(projectPath?: string): string | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Get project directory from .gemini/.env or derive from cwd
|
|
34
|
+
*/
|
|
35
|
+
export declare function getProjectDir(projectPath?: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Get GK project hash from .gemini/.env
|
|
38
|
+
*/
|
|
39
|
+
export declare function getGkProjectHash(projectPath?: string): string | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Get Gemini project hash from .gemini/.env
|
|
42
|
+
*/
|
|
43
|
+
export declare function getGeminiProjectHash(projectPath?: string): string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Get active plan from .gemini/.env
|
|
46
|
+
*/
|
|
47
|
+
export declare function getActivePlan(projectPath?: string): string | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Get suggested plan from .gemini/.env
|
|
50
|
+
*/
|
|
51
|
+
export declare function getSuggestedPlan(projectPath?: string): string | undefined;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment variable utilities for session management
|
|
3
|
+
* Aligned with gk-session-manager.cjs readEnv()
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from 'fs';
|
|
6
|
+
import { getLocalEnvPath, sanitizeProjectPath } from '../../utils/paths.js';
|
|
7
|
+
/**
|
|
8
|
+
* Read environment variables from .gemini/.env
|
|
9
|
+
* Matches gk-session-manager.cjs readEnv()
|
|
10
|
+
*/
|
|
11
|
+
export function readEnv(projectPath = process.cwd()) {
|
|
12
|
+
const result = {
|
|
13
|
+
// GemKit IDs
|
|
14
|
+
ACTIVE_GK_SESSION_ID: '',
|
|
15
|
+
GK_PROJECT_HASH: '',
|
|
16
|
+
PROJECT_DIR: '',
|
|
17
|
+
// Gemini IDs (mapped)
|
|
18
|
+
ACTIVE_GEMINI_SESSION_ID: '',
|
|
19
|
+
GEMINI_PROJECT_HASH: '',
|
|
20
|
+
GEMINI_PARENT_ID: '',
|
|
21
|
+
// Plan info
|
|
22
|
+
ACTIVE_PLAN: '',
|
|
23
|
+
SUGGESTED_PLAN: '',
|
|
24
|
+
PLAN_DATE_FORMAT: ''
|
|
25
|
+
};
|
|
26
|
+
const envPath = getLocalEnvPath(projectPath);
|
|
27
|
+
if (!existsSync(envPath)) {
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const content = readFileSync(envPath, 'utf-8');
|
|
32
|
+
// GemKit IDs
|
|
33
|
+
const gkSessionMatch = content.match(/^ACTIVE_GK_SESSION_ID=(.*)$/m);
|
|
34
|
+
if (gkSessionMatch)
|
|
35
|
+
result.ACTIVE_GK_SESSION_ID = gkSessionMatch[1].trim();
|
|
36
|
+
const gkHashMatch = content.match(/^GK_PROJECT_HASH=(.*)$/m);
|
|
37
|
+
if (gkHashMatch)
|
|
38
|
+
result.GK_PROJECT_HASH = gkHashMatch[1].trim();
|
|
39
|
+
const projectDirMatch = content.match(/^PROJECT_DIR=(.*)$/m);
|
|
40
|
+
if (projectDirMatch)
|
|
41
|
+
result.PROJECT_DIR = projectDirMatch[1].trim();
|
|
42
|
+
// Gemini IDs
|
|
43
|
+
const geminiSessionMatch = content.match(/^ACTIVE_GEMINI_SESSION_ID=(.*)$/m);
|
|
44
|
+
if (geminiSessionMatch)
|
|
45
|
+
result.ACTIVE_GEMINI_SESSION_ID = geminiSessionMatch[1].trim();
|
|
46
|
+
const geminiHashMatch = content.match(/^GEMINI_PROJECT_HASH=(.*)$/m);
|
|
47
|
+
if (geminiHashMatch)
|
|
48
|
+
result.GEMINI_PROJECT_HASH = geminiHashMatch[1].trim();
|
|
49
|
+
const geminiParentMatch = content.match(/^GEMINI_PARENT_ID=(.*)$/m);
|
|
50
|
+
if (geminiParentMatch)
|
|
51
|
+
result.GEMINI_PARENT_ID = geminiParentMatch[1].trim();
|
|
52
|
+
// Plan info
|
|
53
|
+
const activePlanMatch = content.match(/^ACTIVE_PLAN=(.*)$/m);
|
|
54
|
+
if (activePlanMatch)
|
|
55
|
+
result.ACTIVE_PLAN = activePlanMatch[1].trim();
|
|
56
|
+
const suggestedPlanMatch = content.match(/^SUGGESTED_PLAN=(.*)$/m);
|
|
57
|
+
if (suggestedPlanMatch)
|
|
58
|
+
result.SUGGESTED_PLAN = suggestedPlanMatch[1].trim();
|
|
59
|
+
const dateFormatMatch = content.match(/^PLAN_DATE_FORMAT=(.*)$/m);
|
|
60
|
+
if (dateFormatMatch)
|
|
61
|
+
result.PLAN_DATE_FORMAT = dateFormatMatch[1].trim();
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
// Return empty values on error
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get active GK session ID from .gemini/.env
|
|
70
|
+
*/
|
|
71
|
+
export function getActiveGkSessionId(projectPath = process.cwd()) {
|
|
72
|
+
const env = readEnv(projectPath);
|
|
73
|
+
return env.ACTIVE_GK_SESSION_ID || undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get active Gemini session ID from .gemini/.env
|
|
77
|
+
*/
|
|
78
|
+
export function getActiveGeminiSessionId(projectPath = process.cwd()) {
|
|
79
|
+
const env = readEnv(projectPath);
|
|
80
|
+
return env.ACTIVE_GEMINI_SESSION_ID || undefined;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get project directory from .gemini/.env or derive from cwd
|
|
84
|
+
*/
|
|
85
|
+
export function getProjectDir(projectPath = process.cwd()) {
|
|
86
|
+
const env = readEnv(projectPath);
|
|
87
|
+
if (env.PROJECT_DIR)
|
|
88
|
+
return env.PROJECT_DIR;
|
|
89
|
+
return sanitizeProjectPath(projectPath);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get GK project hash from .gemini/.env
|
|
93
|
+
*/
|
|
94
|
+
export function getGkProjectHash(projectPath = process.cwd()) {
|
|
95
|
+
const env = readEnv(projectPath);
|
|
96
|
+
return env.GK_PROJECT_HASH || undefined;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get Gemini project hash from .gemini/.env
|
|
100
|
+
*/
|
|
101
|
+
export function getGeminiProjectHash(projectPath = process.cwd()) {
|
|
102
|
+
const env = readEnv(projectPath);
|
|
103
|
+
return env.GEMINI_PROJECT_HASH || undefined;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get active plan from .gemini/.env
|
|
107
|
+
*/
|
|
108
|
+
export function getActivePlan(projectPath = process.cwd()) {
|
|
109
|
+
const env = readEnv(projectPath);
|
|
110
|
+
return env.ACTIVE_PLAN || undefined;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get suggested plan from .gemini/.env
|
|
114
|
+
*/
|
|
115
|
+
export function getSuggestedPlan(projectPath = process.cwd()) {
|
|
116
|
+
const env = readEnv(projectPath);
|
|
117
|
+
return env.SUGGESTED_PLAN || undefined;
|
|
118
|
+
}
|