climemo 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/dist/commands/ask.d.ts +2 -0
- package/dist/commands/ask.js +37 -0
- package/dist/commands/ask.js.map +1 -0
- package/dist/commands/compare.d.ts +2 -0
- package/dist/commands/compare.js +57 -0
- package/dist/commands/compare.js.map +1 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +37 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/daemon.d.ts +2 -0
- package/dist/commands/daemon.js +69 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/invite.d.ts +2 -0
- package/dist/commands/invite.js +49 -0
- package/dist/commands/invite.js.map +1 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +94 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.js +9 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/members.d.ts +2 -0
- package/dist/commands/members.js +98 -0
- package/dist/commands/members.js.map +1 -0
- package/dist/commands/merge.d.ts +2 -0
- package/dist/commands/merge.js +120 -0
- package/dist/commands/merge.js.map +1 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +32 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/sync.d.ts +2 -0
- package/dist/commands/sync.js +200 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/token.d.ts +2 -0
- package/dist/commands/token.js +60 -0
- package/dist/commands/token.js.map +1 -0
- package/dist/commands/whoami.d.ts +2 -0
- package/dist/commands/whoami.js +20 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.js +27 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/install.d.ts +13 -0
- package/dist/daemon/install.js +143 -0
- package/dist/daemon/install.js.map +1 -0
- package/dist/daemon/logger.d.ts +8 -0
- package/dist/daemon/logger.js +41 -0
- package/dist/daemon/logger.js.map +1 -0
- package/dist/daemon/token-refresher.d.ts +14 -0
- package/dist/daemon/token-refresher.js +88 -0
- package/dist/daemon/token-refresher.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api.d.ts +13 -0
- package/dist/lib/api.js +18 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/auth.d.ts +24 -0
- package/dist/lib/auth.js +72 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/config.d.ts +9 -0
- package/dist/lib/config.js +36 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/git.d.ts +24 -0
- package/dist/lib/git.js +63 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/keychain.d.ts +4 -0
- package/dist/lib/keychain.js +39 -0
- package/dist/lib/keychain.js.map +1 -0
- package/dist/lib/project.d.ts +13 -0
- package/dist/lib/project.js +133 -0
- package/dist/lib/project.js.map +1 -0
- package/dist/lib/server.d.ts +9 -0
- package/dist/lib/server.js +80 -0
- package/dist/lib/server.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { apiRequest } from "../lib/api.js";
|
|
3
|
+
import { resolveProject } from "../lib/project.js";
|
|
4
|
+
export const mergeCommand = new Command("merge")
|
|
5
|
+
.description("Merge a source project into the current repo's project")
|
|
6
|
+
.argument("<sourceProjectId>", "ID of the source project to merge from")
|
|
7
|
+
.option("--force", "Overwrite conflicting docs with the source version")
|
|
8
|
+
.action(async (sourceProjectId, options, cmd) => {
|
|
9
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
10
|
+
try {
|
|
11
|
+
// Auto-detect target project from the current git repo
|
|
12
|
+
const targetProjectId = await resolveProject(globalOpts.token);
|
|
13
|
+
if (sourceProjectId === targetProjectId) {
|
|
14
|
+
console.error(JSON.stringify({ error: "Source and target projects are the same" }));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
// Fetch docs from both projects in parallel
|
|
18
|
+
const [sourceRes, targetRes] = await Promise.all([
|
|
19
|
+
apiRequest(`/api/projects/${sourceProjectId}/documents`, {
|
|
20
|
+
token: globalOpts.token,
|
|
21
|
+
}),
|
|
22
|
+
apiRequest(`/api/projects/${targetProjectId}/documents`, {
|
|
23
|
+
token: globalOpts.token,
|
|
24
|
+
}),
|
|
25
|
+
]);
|
|
26
|
+
if (!sourceRes.ok || !sourceRes.data) {
|
|
27
|
+
console.error(JSON.stringify({
|
|
28
|
+
error: "Failed to fetch source project documents",
|
|
29
|
+
details: sourceRes.data,
|
|
30
|
+
}));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
if (!targetRes.ok || !targetRes.data) {
|
|
34
|
+
console.error(JSON.stringify({
|
|
35
|
+
error: "Failed to fetch target project documents",
|
|
36
|
+
details: targetRes.data,
|
|
37
|
+
}));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const sourceDocs = sourceRes.data.data || [];
|
|
41
|
+
const targetDocs = targetRes.data.data || [];
|
|
42
|
+
const targetPaths = new Set(targetDocs.map((d) => d.path));
|
|
43
|
+
let moved = 0;
|
|
44
|
+
let overwritten = 0;
|
|
45
|
+
let skipped = 0;
|
|
46
|
+
const conflicts = [];
|
|
47
|
+
for (const doc of sourceDocs) {
|
|
48
|
+
const hasConflict = targetPaths.has(doc.path);
|
|
49
|
+
if (hasConflict && !options.force) {
|
|
50
|
+
conflicts.push(doc.path);
|
|
51
|
+
skipped++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (hasConflict && options.force) {
|
|
55
|
+
// Find the target doc to update
|
|
56
|
+
const targetDoc = targetDocs.find((d) => d.path === doc.path);
|
|
57
|
+
if (targetDoc) {
|
|
58
|
+
const { ok } = await apiRequest(`/api/projects/${targetProjectId}/documents/${targetDoc.id}`, {
|
|
59
|
+
method: "PATCH",
|
|
60
|
+
body: {
|
|
61
|
+
content: doc.content,
|
|
62
|
+
title: doc.title,
|
|
63
|
+
change_summary: "Overwritten during merge",
|
|
64
|
+
},
|
|
65
|
+
token: globalOpts.token,
|
|
66
|
+
});
|
|
67
|
+
if (ok)
|
|
68
|
+
overwritten++;
|
|
69
|
+
}
|
|
70
|
+
// Delete from source
|
|
71
|
+
await apiRequest(`/api/projects/${sourceProjectId}/documents/${doc.id}`, { method: "DELETE", token: globalOpts.token });
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// No conflict — create in target
|
|
75
|
+
const { ok } = await apiRequest(`/api/projects/${targetProjectId}/documents`, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
body: {
|
|
78
|
+
title: doc.title,
|
|
79
|
+
path: doc.path,
|
|
80
|
+
content: doc.content,
|
|
81
|
+
updated_via: "cli-merge",
|
|
82
|
+
},
|
|
83
|
+
token: globalOpts.token,
|
|
84
|
+
});
|
|
85
|
+
if (ok)
|
|
86
|
+
moved++;
|
|
87
|
+
// Delete from source
|
|
88
|
+
await apiRequest(`/api/projects/${sourceProjectId}/documents/${doc.id}`, { method: "DELETE", token: globalOpts.token });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Check if source project is now empty, and delete it if so
|
|
92
|
+
let sourceDeleted = false;
|
|
93
|
+
const remainRes = await apiRequest(`/api/projects/${sourceProjectId}/documents`, { token: globalOpts.token });
|
|
94
|
+
const remaining = remainRes.ok && remainRes.data
|
|
95
|
+
? remainRes.data.data || []
|
|
96
|
+
: [];
|
|
97
|
+
if (remaining.length === 0) {
|
|
98
|
+
const delRes = await apiRequest(`/api/projects/${sourceProjectId}`, { method: "DELETE", token: globalOpts.token });
|
|
99
|
+
sourceDeleted = delRes.ok;
|
|
100
|
+
}
|
|
101
|
+
if (conflicts.length > 0) {
|
|
102
|
+
console.error(JSON.stringify({
|
|
103
|
+
warning: "Conflicting docs skipped (use --force to overwrite)",
|
|
104
|
+
conflicts,
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
console.log(JSON.stringify({
|
|
108
|
+
target_project: targetProjectId,
|
|
109
|
+
source_project: sourceProjectId,
|
|
110
|
+
merged: { moved, overwritten, skipped },
|
|
111
|
+
source_deleted: sourceDeleted,
|
|
112
|
+
}, null, 2));
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
116
|
+
console.error(JSON.stringify({ error: message }));
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
//# sourceMappingURL=merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/commands/merge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { apiRequest } from "../lib/api.js";
|
|
3
|
+
import { resolveProject } from "../lib/project.js";
|
|
4
|
+
export const searchCommand = new Command("search")
|
|
5
|
+
.description("Cross-project document search")
|
|
6
|
+
.argument("<query>", "Search query")
|
|
7
|
+
.option("--project <projectId>", "Project ID (auto-detected from git repo if omitted)")
|
|
8
|
+
.option("--all", "Search across all projects, not just current repo")
|
|
9
|
+
.action(async (query, options, cmd) => {
|
|
10
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
11
|
+
try {
|
|
12
|
+
const params = new URLSearchParams({ q: query });
|
|
13
|
+
if (!options.all) {
|
|
14
|
+
const projectId = options.project || await resolveProject(globalOpts.token);
|
|
15
|
+
params.set("project_id", projectId);
|
|
16
|
+
}
|
|
17
|
+
const { ok, data } = await apiRequest(`/api/search?${params}`, {
|
|
18
|
+
token: globalOpts.token,
|
|
19
|
+
});
|
|
20
|
+
if (!ok) {
|
|
21
|
+
console.error(JSON.stringify({ error: "Search failed", details: data }));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
console.log(JSON.stringify(data, null, 2));
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
28
|
+
console.error(JSON.stringify({ error: message }));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC;KAC/C,WAAW,CAAC,+BAA+B,CAAC;KAC5C,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;KACnC,MAAM,CAAC,uBAAuB,EAAE,qDAAqD,CAAC;KACtF,MAAM,CAAC,OAAO,EAAE,mDAAmD,CAAC;KACpE,MAAM,CAAC,KAAK,EAAE,KAAa,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE;IAC5C,MAAM,UAAU,GAAG,GAAG,CAAC,eAAe,EAAE,CAAC;IAEzC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAEjD,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;YACjB,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,IAAI,MAAM,cAAc,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAC5E,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;QACtC,CAAC;QAED,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,MAAM,UAAU,CAAC,eAAe,MAAM,EAAE,EAAE;YAC7D,KAAK,EAAE,UAAU,CAAC,KAAK;SACxB,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import { resolve, relative, basename } from "path";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import { apiRequest } from "../lib/api.js";
|
|
6
|
+
import { resolveProject } from "../lib/project.js";
|
|
7
|
+
import { getGitInfo, parseGitHubRemote } from "../lib/git.js";
|
|
8
|
+
/**
|
|
9
|
+
* Find markdown/text files that are likely project documentation.
|
|
10
|
+
*/
|
|
11
|
+
function findDocFiles(rootPath) {
|
|
12
|
+
try {
|
|
13
|
+
// Use git ls-files to respect .gitignore
|
|
14
|
+
const output = execSync('git ls-files --cached --others --exclude-standard -- "*.md" "*.mdx" "CLAUDE.md" "TASKS.md" "docs/**"', { cwd: rootPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
15
|
+
return output
|
|
16
|
+
.trim()
|
|
17
|
+
.split("\n")
|
|
18
|
+
.filter((f) => f.length > 0)
|
|
19
|
+
.map((f) => resolve(rootPath, f));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export const syncCommand = new Command("sync")
|
|
26
|
+
.description("Sync local docs to your climemo project (auto-detects git repo)")
|
|
27
|
+
.option("--project <projectId>", "Project ID (auto-detected from git repo if omitted)")
|
|
28
|
+
.option("--dry-run", "Show what would be synced without making changes")
|
|
29
|
+
.option("--no-team", "Skip auto-inviting GitHub repo collaborators")
|
|
30
|
+
.option("--pattern <glob>", "Additional file pattern to include (e.g. '**/*.txt')")
|
|
31
|
+
.action(async (options, cmd) => {
|
|
32
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
33
|
+
try {
|
|
34
|
+
const git = getGitInfo();
|
|
35
|
+
if (!git) {
|
|
36
|
+
console.error(JSON.stringify({ error: "Not inside a git repository." }));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
const projectId = options.project || await resolveProject(globalOpts.token);
|
|
40
|
+
// Find doc files
|
|
41
|
+
let files = findDocFiles(git.rootPath);
|
|
42
|
+
// Add extra pattern if specified
|
|
43
|
+
if (options.pattern) {
|
|
44
|
+
try {
|
|
45
|
+
const extra = execSync(`git ls-files --cached --others --exclude-standard -- "${options.pattern}"`, { cwd: git.rootPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
46
|
+
const extraFiles = extra.trim().split("\n").filter((f) => f.length > 0).map((f) => resolve(git.rootPath, f));
|
|
47
|
+
files = [...new Set([...files, ...extraFiles])];
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// ignore
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (files.length === 0) {
|
|
54
|
+
console.log(JSON.stringify({ message: "No documentation files found.", hint: "Add .md files or use --pattern" }));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (options.dryRun) {
|
|
58
|
+
const list = files.map((f) => "/" + relative(git.rootPath, f));
|
|
59
|
+
console.log(JSON.stringify({ dry_run: true, files: list }, null, 2));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Get existing documents in the project
|
|
63
|
+
const { ok: listOk, data: listData } = await apiRequest(`/api/projects/${projectId}/documents`, { token: globalOpts.token });
|
|
64
|
+
const existingDocs = listOk && listData ? listData.data || [] : [];
|
|
65
|
+
const existingByPath = new Map(existingDocs.map((d) => [d.path, d]));
|
|
66
|
+
let created = 0;
|
|
67
|
+
let updated = 0;
|
|
68
|
+
let skipped = 0;
|
|
69
|
+
let conflicts = 0;
|
|
70
|
+
const conflictDetails = [];
|
|
71
|
+
for (const filePath of files) {
|
|
72
|
+
if (!existsSync(filePath))
|
|
73
|
+
continue;
|
|
74
|
+
const content = readFileSync(filePath, "utf-8");
|
|
75
|
+
const docPath = "/" + relative(git.rootPath, filePath);
|
|
76
|
+
const title = basename(filePath);
|
|
77
|
+
const existing = existingByPath.get(docPath);
|
|
78
|
+
if (existing) {
|
|
79
|
+
// Skip if content unchanged
|
|
80
|
+
if (existing.content === content) {
|
|
81
|
+
skipped++;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
// Update with optimistic locking
|
|
85
|
+
const { ok, status, data } = await apiRequest(`/api/projects/${projectId}/documents/${existing.id}`, {
|
|
86
|
+
method: "PATCH",
|
|
87
|
+
body: {
|
|
88
|
+
content,
|
|
89
|
+
title,
|
|
90
|
+
change_summary: "Synced from CLI",
|
|
91
|
+
expected_version: existing.current_version,
|
|
92
|
+
},
|
|
93
|
+
token: globalOpts.token,
|
|
94
|
+
});
|
|
95
|
+
if (ok) {
|
|
96
|
+
updated++;
|
|
97
|
+
}
|
|
98
|
+
else if (status === 409) {
|
|
99
|
+
conflicts++;
|
|
100
|
+
const conflictData = data;
|
|
101
|
+
conflictDetails.push({
|
|
102
|
+
path: docPath,
|
|
103
|
+
reason: conflictData?.message || "Document was modified by another user",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// Create
|
|
109
|
+
const { ok, status } = await apiRequest(`/api/projects/${projectId}/documents`, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
body: { title, path: docPath, content, updated_via: "cli" },
|
|
112
|
+
token: globalOpts.token,
|
|
113
|
+
});
|
|
114
|
+
if (ok) {
|
|
115
|
+
created++;
|
|
116
|
+
}
|
|
117
|
+
else if (status === 409 || status === 422 || status === 500) {
|
|
118
|
+
// Constraint violation (UNIQUE(project_id, path)) — another user
|
|
119
|
+
// created the same doc concurrently. Retry as an update.
|
|
120
|
+
const { ok: retryListOk, data: retryListData } = await apiRequest(`/api/projects/${projectId}/documents`, { token: globalOpts.token });
|
|
121
|
+
if (retryListOk && retryListData) {
|
|
122
|
+
const retryDocs = retryListData.data || [];
|
|
123
|
+
const retryDoc = retryDocs.find((d) => d.path === docPath);
|
|
124
|
+
if (retryDoc) {
|
|
125
|
+
const { ok: updateOk, status: updateStatus, data: updateData } = await apiRequest(`/api/projects/${projectId}/documents/${retryDoc.id}`, {
|
|
126
|
+
method: "PATCH",
|
|
127
|
+
body: {
|
|
128
|
+
content,
|
|
129
|
+
title,
|
|
130
|
+
change_summary: "Synced from CLI",
|
|
131
|
+
expected_version: retryDoc.current_version,
|
|
132
|
+
},
|
|
133
|
+
token: globalOpts.token,
|
|
134
|
+
});
|
|
135
|
+
if (updateOk) {
|
|
136
|
+
updated++;
|
|
137
|
+
}
|
|
138
|
+
else if (updateStatus === 409) {
|
|
139
|
+
conflicts++;
|
|
140
|
+
const conflictData = updateData;
|
|
141
|
+
conflictDetails.push({
|
|
142
|
+
path: docPath,
|
|
143
|
+
reason: conflictData?.message || "Document was modified by another user during sync",
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const result = {
|
|
152
|
+
project_id: projectId,
|
|
153
|
+
synced: { created, updated, skipped, conflicts, total: files.length },
|
|
154
|
+
};
|
|
155
|
+
if (conflictDetails.length > 0) {
|
|
156
|
+
result.conflict_details = conflictDetails;
|
|
157
|
+
}
|
|
158
|
+
// Auto-invite GitHub collaborators (default: on, skip with --no-team)
|
|
159
|
+
if (options.team !== false) {
|
|
160
|
+
const ghRepo = parseGitHubRemote(git.remoteUrl);
|
|
161
|
+
if (!ghRepo) {
|
|
162
|
+
result.team = { error: "Not a GitHub repository. --team requires a GitHub remote." };
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// Get the project's organization_id
|
|
166
|
+
const { ok: projOk, data: projData } = await apiRequest(`/api/projects/${projectId}`, { token: globalOpts.token });
|
|
167
|
+
const orgId = projOk && projData
|
|
168
|
+
? projData.data?.organization_id
|
|
169
|
+
: null;
|
|
170
|
+
if (!orgId) {
|
|
171
|
+
result.team = { error: "Could not determine workspace for auto-invite." };
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
const { ok: invOk, data: invData } = await apiRequest("/api/organizations/auto-invite", {
|
|
175
|
+
method: "POST",
|
|
176
|
+
body: {
|
|
177
|
+
github_owner: ghRepo.owner,
|
|
178
|
+
github_repo: ghRepo.repo,
|
|
179
|
+
organization_id: orgId,
|
|
180
|
+
},
|
|
181
|
+
token: globalOpts.token,
|
|
182
|
+
});
|
|
183
|
+
if (invOk && invData) {
|
|
184
|
+
result.team = invData.data;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
result.team = { error: "Auto-invite failed", details: invData };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
console.log(JSON.stringify(result, null, 2));
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
196
|
+
console.error(JSON.stringify({ error: message }));
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { getAccessToken, getRefreshToken } from "../lib/keychain.js";
|
|
3
|
+
import { refreshAccessToken, getAuthHeaders } from "../lib/auth.js";
|
|
4
|
+
import { getConfig } from "../lib/config.js";
|
|
5
|
+
export const tokenCommand = new Command("token")
|
|
6
|
+
.description("Token management");
|
|
7
|
+
tokenCommand
|
|
8
|
+
.command("create")
|
|
9
|
+
.description("Create a new API token (for CI/CD)")
|
|
10
|
+
.option("--name <name>", "Token name", "ci-token")
|
|
11
|
+
.option("--expires <duration>", "Expiry duration", "24h")
|
|
12
|
+
.action(async (options, cmd) => {
|
|
13
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
14
|
+
const config = getConfig();
|
|
15
|
+
try {
|
|
16
|
+
const headers = await getAuthHeaders(globalOpts.token);
|
|
17
|
+
const res = await fetch(`${config.apiUrl}/api/cli/token/create`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers,
|
|
20
|
+
body: JSON.stringify({ name: options.name, expires: options.expires }),
|
|
21
|
+
});
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
console.error("Failed to create token:", await res.text());
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
const data = await res.json();
|
|
27
|
+
console.log(JSON.stringify(data, null, 2));
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
31
|
+
console.error("Error:", message);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
tokenCommand
|
|
36
|
+
.command("refresh")
|
|
37
|
+
.description("Manually refresh the access token")
|
|
38
|
+
.action(async () => {
|
|
39
|
+
const success = await refreshAccessToken();
|
|
40
|
+
if (success) {
|
|
41
|
+
console.log("Token refreshed and stored in Keychain.");
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.log("Failed to refresh token. Run `climemo login` to re-authenticate.");
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
tokenCommand
|
|
49
|
+
.command("status")
|
|
50
|
+
.description("Check token status")
|
|
51
|
+
.action(async () => {
|
|
52
|
+
const accessToken = await getAccessToken();
|
|
53
|
+
const refreshToken = await getRefreshToken();
|
|
54
|
+
console.log(JSON.stringify({
|
|
55
|
+
access_token: accessToken ? "stored" : "none",
|
|
56
|
+
refresh_token: refreshToken ? "stored" : "none",
|
|
57
|
+
storage: "os_keychain",
|
|
58
|
+
}, null, 2));
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/commands/token.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { resolveToken, validateToken } from "../lib/auth.js";
|
|
3
|
+
export const whoamiCommand = new Command("whoami")
|
|
4
|
+
.description("Show current authenticated user")
|
|
5
|
+
.action(async (_, cmd) => {
|
|
6
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
7
|
+
const token = await resolveToken(globalOpts.token);
|
|
8
|
+
if (!token) {
|
|
9
|
+
console.log("Not logged in. Run `climemo login` to authenticate.");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const { valid, email, expires_at } = await validateToken(token);
|
|
13
|
+
if (!valid) {
|
|
14
|
+
console.log("Token is invalid or expired. Run `climemo login` to re-authenticate.");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
// JSON output (default for CLI, agent-friendly)
|
|
18
|
+
console.log(JSON.stringify({ email, expires_at, source: globalOpts.token ? "flag" : process.env.CLIMEMO_TOKEN ? "env" : "keychain" }, null, 2));
|
|
19
|
+
});
|
|
20
|
+
//# sourceMappingURL=whoami.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE7D,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC;KAC/C,WAAW,CAAC,iCAAiC,CAAC;KAC9C,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE;IACvB,MAAM,UAAU,GAAG,GAAG,CAAC,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IAEnD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;QACnE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;IAEhE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,sEAAsE,CAAC,CAAC;QACpF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,gDAAgD;IAChD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAClJ,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { TokenRefresher } from "./token-refresher.js";
|
|
3
|
+
import { DaemonLogger } from "./logger.js";
|
|
4
|
+
const logger = new DaemonLogger();
|
|
5
|
+
async function main() {
|
|
6
|
+
logger.info("Climemo daemon starting...");
|
|
7
|
+
const tokenRefresher = new TokenRefresher(logger);
|
|
8
|
+
// Start token refresh loop (check every 30 minutes)
|
|
9
|
+
tokenRefresher.start(30 * 60 * 1000);
|
|
10
|
+
// Handle graceful shutdown
|
|
11
|
+
process.on("SIGINT", () => {
|
|
12
|
+
logger.info("Daemon shutting down...");
|
|
13
|
+
tokenRefresher.stop();
|
|
14
|
+
process.exit(0);
|
|
15
|
+
});
|
|
16
|
+
process.on("SIGTERM", () => {
|
|
17
|
+
logger.info("Daemon shutting down...");
|
|
18
|
+
tokenRefresher.stop();
|
|
19
|
+
process.exit(0);
|
|
20
|
+
});
|
|
21
|
+
logger.info("Daemon started. Token auto-refresh active.");
|
|
22
|
+
}
|
|
23
|
+
main().catch((err) => {
|
|
24
|
+
logger.error("Daemon failed to start:", err);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/daemon/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;AAElC,KAAK,UAAU,IAAI;IACjB,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;IAE1C,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;IAElD,oDAAoD;IACpD,cAAc,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAErC,2BAA2B;IAC3B,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACvC,cAAc,CAAC,IAAI,EAAE,CAAC;QACtB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACvC,cAAc,CAAC,IAAI,EAAE,CAAC;QACtB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;AAC5D,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,MAAM,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;IAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DaemonLogger } from "./logger.js";
|
|
2
|
+
/**
|
|
3
|
+
* Install the daemon as a launchd service (macOS)
|
|
4
|
+
*/
|
|
5
|
+
export declare function installDaemon(logger?: DaemonLogger): void;
|
|
6
|
+
/**
|
|
7
|
+
* Uninstall the daemon
|
|
8
|
+
*/
|
|
9
|
+
export declare function uninstallDaemon(logger?: DaemonLogger): void;
|
|
10
|
+
/**
|
|
11
|
+
* Check if daemon is running
|
|
12
|
+
*/
|
|
13
|
+
export declare function isDaemonRunning(): boolean;
|