@digitalpresence/cliclaw 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/__tests__/calendar.integration.test.d.ts +2 -0
- package/dist/__tests__/calendar.integration.test.d.ts.map +1 -0
- package/dist/__tests__/calendar.integration.test.js +98 -0
- package/dist/__tests__/calendar.integration.test.js.map +1 -0
- package/dist/__tests__/forms.integration.test.d.ts +2 -0
- package/dist/__tests__/forms.integration.test.d.ts.map +1 -0
- package/dist/__tests__/forms.integration.test.js +243 -0
- package/dist/__tests__/forms.integration.test.js.map +1 -0
- package/dist/__tests__/gdrive.integration.test.d.ts +2 -0
- package/dist/__tests__/gdrive.integration.test.d.ts.map +1 -0
- package/dist/__tests__/gdrive.integration.test.js +186 -0
- package/dist/__tests__/gdrive.integration.test.js.map +1 -0
- package/dist/__tests__/gmail.integration.test.d.ts +2 -0
- package/dist/__tests__/gmail.integration.test.d.ts.map +1 -0
- package/dist/__tests__/gmail.integration.test.js +197 -0
- package/dist/__tests__/gmail.integration.test.js.map +1 -0
- package/dist/__tests__/gslides.integration.test.d.ts +2 -0
- package/dist/__tests__/gslides.integration.test.d.ts.map +1 -0
- package/dist/__tests__/gslides.integration.test.js +124 -0
- package/dist/__tests__/gslides.integration.test.js.map +1 -0
- package/dist/__tests__/sheets.integration.test.d.ts +2 -0
- package/dist/__tests__/sheets.integration.test.d.ts.map +1 -0
- package/dist/__tests__/sheets.integration.test.js +150 -0
- package/dist/__tests__/sheets.integration.test.js.map +1 -0
- package/dist/agent/crud.d.ts +6 -0
- package/dist/agent/crud.d.ts.map +1 -0
- package/dist/agent/crud.js +41 -0
- package/dist/agent/crud.js.map +1 -0
- package/dist/agent/memory.d.ts +14 -0
- package/dist/agent/memory.d.ts.map +1 -0
- package/dist/agent/memory.js +66 -0
- package/dist/agent/memory.js.map +1 -0
- package/dist/agent/permissions.d.ts +4 -0
- package/dist/agent/permissions.d.ts.map +1 -0
- package/dist/agent/permissions.js +32 -0
- package/dist/agent/permissions.js.map +1 -0
- package/dist/calendar/accounts.d.ts +3 -0
- package/dist/calendar/accounts.d.ts.map +1 -0
- package/dist/calendar/accounts.js +21 -0
- package/dist/calendar/accounts.js.map +1 -0
- package/dist/calendar/auth.d.ts +3 -0
- package/dist/calendar/auth.d.ts.map +1 -0
- package/dist/calendar/auth.js +41 -0
- package/dist/calendar/auth.js.map +1 -0
- package/dist/calendar/calendars.d.ts +3 -0
- package/dist/calendar/calendars.d.ts.map +1 -0
- package/dist/calendar/calendars.js +28 -0
- package/dist/calendar/calendars.js.map +1 -0
- package/dist/calendar/create.d.ts +3 -0
- package/dist/calendar/create.d.ts.map +1 -0
- package/dist/calendar/create.js +31 -0
- package/dist/calendar/create.js.map +1 -0
- package/dist/calendar/delete.d.ts +3 -0
- package/dist/calendar/delete.d.ts.map +1 -0
- package/dist/calendar/delete.js +21 -0
- package/dist/calendar/delete.js.map +1 -0
- package/dist/calendar/events.d.ts +3 -0
- package/dist/calendar/events.d.ts.map +1 -0
- package/dist/calendar/events.js +37 -0
- package/dist/calendar/events.js.map +1 -0
- package/dist/calendar/get.d.ts +3 -0
- package/dist/calendar/get.d.ts.map +1 -0
- package/dist/calendar/get.js +21 -0
- package/dist/calendar/get.js.map +1 -0
- package/dist/calendar/update.d.ts +3 -0
- package/dist/calendar/update.d.ts.map +1 -0
- package/dist/calendar/update.js +31 -0
- package/dist/calendar/update.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +37 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/agent.d.ts +6 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +107 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/calendar.d.ts +9 -0
- package/dist/commands/calendar.d.ts.map +1 -0
- package/dist/commands/calendar.js +104 -0
- package/dist/commands/calendar.js.map +1 -0
- package/dist/commands/cron.d.ts +6 -0
- package/dist/commands/cron.d.ts.map +1 -0
- package/dist/commands/cron.js +103 -0
- package/dist/commands/cron.js.map +1 -0
- package/dist/commands/forms.d.ts +9 -0
- package/dist/commands/forms.d.ts.map +1 -0
- package/dist/commands/forms.js +139 -0
- package/dist/commands/forms.js.map +1 -0
- package/dist/commands/gdrive.d.ts +9 -0
- package/dist/commands/gdrive.d.ts.map +1 -0
- package/dist/commands/gdrive.js +198 -0
- package/dist/commands/gdrive.js.map +1 -0
- package/dist/commands/gmail.d.ts +9 -0
- package/dist/commands/gmail.d.ts.map +1 -0
- package/dist/commands/gmail.js +231 -0
- package/dist/commands/gmail.js.map +1 -0
- package/dist/commands/gslides.d.ts +9 -0
- package/dist/commands/gslides.d.ts.map +1 -0
- package/dist/commands/gslides.js +167 -0
- package/dist/commands/gslides.js.map +1 -0
- package/dist/commands/sheets.d.ts +9 -0
- package/dist/commands/sheets.d.ts.map +1 -0
- package/dist/commands/sheets.js +174 -0
- package/dist/commands/sheets.js.map +1 -0
- package/dist/cron/daemon.d.ts +3 -0
- package/dist/cron/daemon.d.ts.map +1 -0
- package/dist/cron/daemon.js +127 -0
- package/dist/cron/daemon.js.map +1 -0
- package/dist/cron/handlers.d.ts +6 -0
- package/dist/cron/handlers.d.ts.map +1 -0
- package/dist/cron/handlers.js +58 -0
- package/dist/cron/handlers.js.map +1 -0
- package/dist/cron/logger.d.ts +4 -0
- package/dist/cron/logger.d.ts.map +1 -0
- package/dist/cron/logger.js +11 -0
- package/dist/cron/logger.js.map +1 -0
- package/dist/cron/progress.d.ts +25 -0
- package/dist/cron/progress.d.ts.map +1 -0
- package/dist/cron/progress.js +71 -0
- package/dist/cron/progress.js.map +1 -0
- package/dist/cron/ralph-wiggum.d.ts +18 -0
- package/dist/cron/ralph-wiggum.d.ts.map +1 -0
- package/dist/cron/ralph-wiggum.js +143 -0
- package/dist/cron/ralph-wiggum.js.map +1 -0
- package/dist/forms/accounts.d.ts +3 -0
- package/dist/forms/accounts.d.ts.map +1 -0
- package/dist/forms/accounts.js +20 -0
- package/dist/forms/accounts.js.map +1 -0
- package/dist/forms/auth.d.ts +3 -0
- package/dist/forms/auth.d.ts.map +1 -0
- package/dist/forms/auth.js +41 -0
- package/dist/forms/auth.js.map +1 -0
- package/dist/forms/create.d.ts +3 -0
- package/dist/forms/create.d.ts.map +1 -0
- package/dist/forms/create.js +28 -0
- package/dist/forms/create.js.map +1 -0
- package/dist/forms/get.d.ts +3 -0
- package/dist/forms/get.d.ts.map +1 -0
- package/dist/forms/get.js +21 -0
- package/dist/forms/get.js.map +1 -0
- package/dist/forms/list.d.ts +3 -0
- package/dist/forms/list.d.ts.map +1 -0
- package/dist/forms/list.js +33 -0
- package/dist/forms/list.js.map +1 -0
- package/dist/forms/questions.d.ts +6 -0
- package/dist/forms/questions.d.ts.map +1 -0
- package/dist/forms/questions.js +179 -0
- package/dist/forms/questions.js.map +1 -0
- package/dist/forms/responses.d.ts +4 -0
- package/dist/forms/responses.d.ts.map +1 -0
- package/dist/forms/responses.js +40 -0
- package/dist/forms/responses.js.map +1 -0
- package/dist/forms/update.d.ts +3 -0
- package/dist/forms/update.d.ts.map +1 -0
- package/dist/forms/update.js +44 -0
- package/dist/forms/update.js.map +1 -0
- package/dist/gdrive/about.d.ts +3 -0
- package/dist/gdrive/about.d.ts.map +1 -0
- package/dist/gdrive/about.js +31 -0
- package/dist/gdrive/about.js.map +1 -0
- package/dist/gdrive/accounts.d.ts +3 -0
- package/dist/gdrive/accounts.d.ts.map +1 -0
- package/dist/gdrive/accounts.js +20 -0
- package/dist/gdrive/accounts.js.map +1 -0
- package/dist/gdrive/auth.d.ts +3 -0
- package/dist/gdrive/auth.d.ts.map +1 -0
- package/dist/gdrive/auth.js +51 -0
- package/dist/gdrive/auth.js.map +1 -0
- package/dist/gdrive/files.d.ts +12 -0
- package/dist/gdrive/files.d.ts.map +1 -0
- package/dist/gdrive/files.js +174 -0
- package/dist/gdrive/files.js.map +1 -0
- package/dist/gdrive/folders.d.ts +4 -0
- package/dist/gdrive/folders.d.ts.map +1 -0
- package/dist/gdrive/folders.js +46 -0
- package/dist/gdrive/folders.js.map +1 -0
- package/dist/gdrive/search.d.ts +3 -0
- package/dist/gdrive/search.d.ts.map +1 -0
- package/dist/gdrive/search.js +23 -0
- package/dist/gdrive/search.js.map +1 -0
- package/dist/gdrive/sharing.d.ts +5 -0
- package/dist/gdrive/sharing.d.ts.map +1 -0
- package/dist/gdrive/sharing.js +54 -0
- package/dist/gdrive/sharing.js.map +1 -0
- package/dist/gmail/accounts.d.ts +3 -0
- package/dist/gmail/accounts.d.ts.map +1 -0
- package/dist/gmail/accounts.js +18 -0
- package/dist/gmail/accounts.js.map +1 -0
- package/dist/gmail/auth.d.ts +3 -0
- package/dist/gmail/auth.d.ts.map +1 -0
- package/dist/gmail/auth.js +50 -0
- package/dist/gmail/auth.js.map +1 -0
- package/dist/gmail/drafts.d.ts +7 -0
- package/dist/gmail/drafts.d.ts.map +1 -0
- package/dist/gmail/drafts.js +103 -0
- package/dist/gmail/drafts.js.map +1 -0
- package/dist/gmail/get.d.ts +4 -0
- package/dist/gmail/get.d.ts.map +1 -0
- package/dist/gmail/get.js +140 -0
- package/dist/gmail/get.js.map +1 -0
- package/dist/gmail/inbox.d.ts +4 -0
- package/dist/gmail/inbox.d.ts.map +1 -0
- package/dist/gmail/inbox.js +45 -0
- package/dist/gmail/inbox.js.map +1 -0
- package/dist/gmail/labels.d.ts +5 -0
- package/dist/gmail/labels.d.ts.map +1 -0
- package/dist/gmail/labels.js +45 -0
- package/dist/gmail/labels.js.map +1 -0
- package/dist/gmail/modify.d.ts +5 -0
- package/dist/gmail/modify.d.ts.map +1 -0
- package/dist/gmail/modify.js +68 -0
- package/dist/gmail/modify.js.map +1 -0
- package/dist/gmail/send.d.ts +5 -0
- package/dist/gmail/send.d.ts.map +1 -0
- package/dist/gmail/send.js +310 -0
- package/dist/gmail/send.js.map +1 -0
- package/dist/gmail/threads.d.ts +4 -0
- package/dist/gmail/threads.d.ts.map +1 -0
- package/dist/gmail/threads.js +47 -0
- package/dist/gmail/threads.js.map +1 -0
- package/dist/gslides/accounts.d.ts +3 -0
- package/dist/gslides/accounts.d.ts.map +1 -0
- package/dist/gslides/accounts.js +20 -0
- package/dist/gslides/accounts.js.map +1 -0
- package/dist/gslides/auth.d.ts +3 -0
- package/dist/gslides/auth.d.ts.map +1 -0
- package/dist/gslides/auth.js +50 -0
- package/dist/gslides/auth.js.map +1 -0
- package/dist/gslides/presentations.d.ts +29 -0
- package/dist/gslides/presentations.d.ts.map +1 -0
- package/dist/gslides/presentations.js +320 -0
- package/dist/gslides/presentations.js.map +1 -0
- package/dist/lib/config.d.ts +6 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +12 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/media-utils.d.ts +5 -0
- package/dist/lib/media-utils.d.ts.map +1 -0
- package/dist/lib/media-utils.js +79 -0
- package/dist/lib/media-utils.js.map +1 -0
- package/dist/lib/output.d.ts +4 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +12 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/sheets/accounts.d.ts +3 -0
- package/dist/sheets/accounts.d.ts.map +1 -0
- package/dist/sheets/accounts.js +20 -0
- package/dist/sheets/accounts.js.map +1 -0
- package/dist/sheets/auth.d.ts +3 -0
- package/dist/sheets/auth.d.ts.map +1 -0
- package/dist/sheets/auth.js +56 -0
- package/dist/sheets/auth.js.map +1 -0
- package/dist/sheets/cells.d.ts +6 -0
- package/dist/sheets/cells.d.ts.map +1 -0
- package/dist/sheets/cells.js +89 -0
- package/dist/sheets/cells.js.map +1 -0
- package/dist/sheets/format.d.ts +8 -0
- package/dist/sheets/format.d.ts.map +1 -0
- package/dist/sheets/format.js +97 -0
- package/dist/sheets/format.js.map +1 -0
- package/dist/sheets/sheets-tab.d.ts +6 -0
- package/dist/sheets/sheets-tab.d.ts.map +1 -0
- package/dist/sheets/sheets-tab.js +88 -0
- package/dist/sheets/sheets-tab.js.map +1 -0
- package/dist/sheets/spreadsheets.d.ts +6 -0
- package/dist/sheets/spreadsheets.d.ts.map +1 -0
- package/dist/sheets/spreadsheets.js +88 -0
- package/dist/sheets/spreadsheets.js.map +1 -0
- package/package.json +33 -0
- package/src/__tests__/calendar.integration.test.ts +152 -0
- package/src/__tests__/forms.integration.test.ts +403 -0
- package/src/__tests__/gdrive.integration.test.ts +253 -0
- package/src/__tests__/gmail.integration.test.ts +294 -0
- package/src/__tests__/gslides.integration.test.ts +195 -0
- package/src/__tests__/sheets.integration.test.ts +234 -0
- package/src/agent/crud.ts +54 -0
- package/src/agent/memory.ts +95 -0
- package/src/agent/permissions.ts +54 -0
- package/src/calendar/accounts.ts +25 -0
- package/src/calendar/auth.ts +45 -0
- package/src/calendar/calendars.ts +32 -0
- package/src/calendar/create.ts +44 -0
- package/src/calendar/delete.ts +27 -0
- package/src/calendar/events.ts +45 -0
- package/src/calendar/get.ts +27 -0
- package/src/calendar/update.ts +44 -0
- package/src/cli.ts +41 -0
- package/src/commands/agent.ts +128 -0
- package/src/commands/calendar.ts +127 -0
- package/src/commands/cron.ts +126 -0
- package/src/commands/forms.ts +158 -0
- package/src/commands/gdrive.ts +225 -0
- package/src/commands/gmail.ts +290 -0
- package/src/commands/gslides.ts +188 -0
- package/src/commands/sheets.ts +193 -0
- package/src/cron/daemon.ts +143 -0
- package/src/cron/handlers.ts +78 -0
- package/src/cron/logger.ts +20 -0
- package/src/cron/progress.ts +90 -0
- package/src/cron/ralph-wiggum.ts +172 -0
- package/src/forms/accounts.ts +24 -0
- package/src/forms/auth.ts +45 -0
- package/src/forms/create.ts +34 -0
- package/src/forms/get.ts +26 -0
- package/src/forms/list.ts +38 -0
- package/src/forms/questions.ts +209 -0
- package/src/forms/responses.ts +50 -0
- package/src/forms/update.ts +57 -0
- package/src/gdrive/about.ts +36 -0
- package/src/gdrive/accounts.ts +24 -0
- package/src/gdrive/auth.ts +55 -0
- package/src/gdrive/files.ts +237 -0
- package/src/gdrive/folders.ts +58 -0
- package/src/gdrive/search.ts +30 -0
- package/src/gdrive/sharing.ts +72 -0
- package/src/gmail/accounts.ts +22 -0
- package/src/gmail/auth.ts +53 -0
- package/src/gmail/drafts.ts +166 -0
- package/src/gmail/get.ts +195 -0
- package/src/gmail/inbox.ts +78 -0
- package/src/gmail/labels.ts +69 -0
- package/src/gmail/modify.ts +89 -0
- package/src/gmail/send.ts +424 -0
- package/src/gmail/threads.ts +68 -0
- package/src/gslides/accounts.ts +24 -0
- package/src/gslides/auth.ts +54 -0
- package/src/gslides/presentations.ts +384 -0
- package/src/lib/config.ts +14 -0
- package/src/lib/media-utils.ts +82 -0
- package/src/lib/output.ts +13 -0
- package/src/sheets/accounts.ts +24 -0
- package/src/sheets/auth.ts +60 -0
- package/src/sheets/cells.ts +112 -0
- package/src/sheets/format.ts +114 -0
- package/src/sheets/sheets-tab.ts +109 -0
- package/src/sheets/spreadsheets.ts +106 -0
- package/tsconfig.json +8 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { describe, it, expect } from "vitest";
|
|
5
|
+
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const CLI = join(import.meta.dirname, "../../dist/cli.js");
|
|
8
|
+
|
|
9
|
+
interface CliResult {
|
|
10
|
+
stdout: string;
|
|
11
|
+
stderr: string;
|
|
12
|
+
exitCode: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function run(...args: string[]): Promise<CliResult> {
|
|
16
|
+
try {
|
|
17
|
+
const { stdout, stderr } = await execFileAsync("node", [CLI, ...args], {
|
|
18
|
+
timeout: 30000,
|
|
19
|
+
});
|
|
20
|
+
return { stdout, stderr, exitCode: 0 };
|
|
21
|
+
} catch (err: any) {
|
|
22
|
+
return {
|
|
23
|
+
stdout: err.stdout ?? "",
|
|
24
|
+
stderr: err.stderr ?? "",
|
|
25
|
+
exitCode: err.code ?? 1,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseJson(result: CliResult): any {
|
|
31
|
+
return JSON.parse(result.stdout);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe.sequential("Google Slides CLI integration", () => {
|
|
35
|
+
const timestamp = Date.now();
|
|
36
|
+
|
|
37
|
+
let presentationId: string;
|
|
38
|
+
let firstSlideId: string;
|
|
39
|
+
let addedSlideId: string;
|
|
40
|
+
|
|
41
|
+
it("accounts — returns a list of gslides accounts", async () => {
|
|
42
|
+
const result = await run("gslides", "accounts");
|
|
43
|
+
expect(result.exitCode).toBe(0);
|
|
44
|
+
|
|
45
|
+
const data = parseJson(result);
|
|
46
|
+
expect(data).toHaveProperty("accounts");
|
|
47
|
+
expect(Array.isArray(data.accounts)).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("create — creates a test presentation", async () => {
|
|
51
|
+
const title = `cliclaw-test-${timestamp}`;
|
|
52
|
+
const result = await run("gslides", "create", "--title", title);
|
|
53
|
+
expect(result.exitCode).toBe(0);
|
|
54
|
+
|
|
55
|
+
const data = parseJson(result);
|
|
56
|
+
expect(data.presentationId).toBeTruthy();
|
|
57
|
+
expect(data.title).toBe(title);
|
|
58
|
+
expect(data.slideCount).toBeGreaterThanOrEqual(1);
|
|
59
|
+
|
|
60
|
+
presentationId = data.presentationId;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("list — returns presentations including our test presentation", async () => {
|
|
64
|
+
const result = await run("gslides", "list", "--max-results", "10");
|
|
65
|
+
expect(result.exitCode).toBe(0);
|
|
66
|
+
|
|
67
|
+
const files = parseJson(result);
|
|
68
|
+
expect(Array.isArray(files)).toBe(true);
|
|
69
|
+
|
|
70
|
+
const ids = files.map((f: any) => f.id);
|
|
71
|
+
expect(ids).toContain(presentationId);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("get — returns presentation details", async () => {
|
|
75
|
+
const result = await run("gslides", "get", "--id", presentationId);
|
|
76
|
+
expect(result.exitCode).toBe(0);
|
|
77
|
+
|
|
78
|
+
const data = parseJson(result);
|
|
79
|
+
expect(data.presentationId).toBe(presentationId);
|
|
80
|
+
expect(data.title).toBe(`cliclaw-test-${timestamp}`);
|
|
81
|
+
expect(data.slideCount).toBeGreaterThanOrEqual(1);
|
|
82
|
+
expect(Array.isArray(data.slides)).toBe(true);
|
|
83
|
+
|
|
84
|
+
firstSlideId = data.slides[0].objectId;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("get-slide — returns details of the first slide", async () => {
|
|
88
|
+
const result = await run(
|
|
89
|
+
"gslides", "get-slide",
|
|
90
|
+
"--id", presentationId,
|
|
91
|
+
"--index", "0",
|
|
92
|
+
);
|
|
93
|
+
expect(result.exitCode).toBe(0);
|
|
94
|
+
|
|
95
|
+
const data = parseJson(result);
|
|
96
|
+
expect(data.objectId).toBe(firstSlideId);
|
|
97
|
+
expect(data.index).toBe(0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("add-slide — adds a blank slide", async () => {
|
|
101
|
+
const result = await run(
|
|
102
|
+
"gslides", "add-slide",
|
|
103
|
+
"--id", presentationId,
|
|
104
|
+
"--layout", "BLANK",
|
|
105
|
+
);
|
|
106
|
+
expect(result.exitCode).toBe(0);
|
|
107
|
+
|
|
108
|
+
const data = parseJson(result);
|
|
109
|
+
expect(data.success).toBe(true);
|
|
110
|
+
expect(data.objectId).toBeTruthy();
|
|
111
|
+
|
|
112
|
+
addedSlideId = data.objectId;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("add-text — adds a text box to the added slide", async () => {
|
|
116
|
+
const result = await run(
|
|
117
|
+
"gslides", "add-text",
|
|
118
|
+
"--id", presentationId,
|
|
119
|
+
"--slide-id", addedSlideId,
|
|
120
|
+
"--text", `Test text ${timestamp}`,
|
|
121
|
+
"--x", "50",
|
|
122
|
+
"--y", "50",
|
|
123
|
+
"--width", "300",
|
|
124
|
+
"--height", "40",
|
|
125
|
+
);
|
|
126
|
+
expect(result.exitCode).toBe(0);
|
|
127
|
+
|
|
128
|
+
const data = parseJson(result);
|
|
129
|
+
expect(data.success).toBe(true);
|
|
130
|
+
expect(data.objectId).toBeTruthy();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("add-shape — adds a rectangle shape to the added slide", async () => {
|
|
134
|
+
const result = await run(
|
|
135
|
+
"gslides", "add-shape",
|
|
136
|
+
"--id", presentationId,
|
|
137
|
+
"--slide-id", addedSlideId,
|
|
138
|
+
"--shape", "RECTANGLE",
|
|
139
|
+
"--x", "50",
|
|
140
|
+
"--y", "150",
|
|
141
|
+
"--width", "200",
|
|
142
|
+
"--height", "100",
|
|
143
|
+
);
|
|
144
|
+
expect(result.exitCode).toBe(0);
|
|
145
|
+
|
|
146
|
+
const data = parseJson(result);
|
|
147
|
+
expect(data.success).toBe(true);
|
|
148
|
+
expect(data.objectId).toBeTruthy();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("duplicate-slide — duplicates the added slide", async () => {
|
|
152
|
+
const result = await run(
|
|
153
|
+
"gslides", "duplicate-slide",
|
|
154
|
+
"--id", presentationId,
|
|
155
|
+
"--slide-id", addedSlideId,
|
|
156
|
+
);
|
|
157
|
+
expect(result.exitCode).toBe(0);
|
|
158
|
+
|
|
159
|
+
const data = parseJson(result);
|
|
160
|
+
expect(data.success).toBe(true);
|
|
161
|
+
expect(data.originalId).toBe(addedSlideId);
|
|
162
|
+
expect(data.newId).toBeTruthy();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("get — confirms presentation now has more slides", async () => {
|
|
166
|
+
const result = await run("gslides", "get", "--id", presentationId);
|
|
167
|
+
expect(result.exitCode).toBe(0);
|
|
168
|
+
|
|
169
|
+
const data = parseJson(result);
|
|
170
|
+
// Original slide + added slide + duplicated slide = at least 3
|
|
171
|
+
expect(data.slideCount).toBeGreaterThanOrEqual(3);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("delete-slide — deletes the added slide", async () => {
|
|
175
|
+
const result = await run(
|
|
176
|
+
"gslides", "delete-slide",
|
|
177
|
+
"--id", presentationId,
|
|
178
|
+
"--slide-id", addedSlideId,
|
|
179
|
+
);
|
|
180
|
+
expect(result.exitCode).toBe(0);
|
|
181
|
+
|
|
182
|
+
const data = parseJson(result);
|
|
183
|
+
expect(data.success).toBe(true);
|
|
184
|
+
expect(data.objectId).toBe(addedSlideId);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("delete — permanently deletes the test presentation", async () => {
|
|
188
|
+
const result = await run("gslides", "delete", "--id", presentationId);
|
|
189
|
+
expect(result.exitCode).toBe(0);
|
|
190
|
+
|
|
191
|
+
const data = parseJson(result);
|
|
192
|
+
expect(data.success).toBe(true);
|
|
193
|
+
expect(data.presentationId).toBe(presentationId);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { describe, it, expect } from "vitest";
|
|
5
|
+
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const CLI = join(import.meta.dirname, "../../dist/cli.js");
|
|
8
|
+
|
|
9
|
+
interface CliResult {
|
|
10
|
+
stdout: string;
|
|
11
|
+
stderr: string;
|
|
12
|
+
exitCode: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function run(...args: string[]): Promise<CliResult> {
|
|
16
|
+
try {
|
|
17
|
+
const { stdout, stderr } = await execFileAsync("node", [CLI, ...args], {
|
|
18
|
+
timeout: 30000,
|
|
19
|
+
});
|
|
20
|
+
return { stdout, stderr, exitCode: 0 };
|
|
21
|
+
} catch (err: any) {
|
|
22
|
+
return {
|
|
23
|
+
stdout: err.stdout ?? "",
|
|
24
|
+
stderr: err.stderr ?? "",
|
|
25
|
+
exitCode: err.code ?? 1,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseJson(result: CliResult): any {
|
|
31
|
+
return JSON.parse(result.stdout);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe.sequential("Google Sheets CLI integration", () => {
|
|
35
|
+
const timestamp = Date.now();
|
|
36
|
+
|
|
37
|
+
let spreadsheetId: string;
|
|
38
|
+
let addedSheetId: number;
|
|
39
|
+
|
|
40
|
+
it("accounts — returns a list of gsheets accounts", async () => {
|
|
41
|
+
const result = await run("sheets", "accounts");
|
|
42
|
+
expect(result.exitCode).toBe(0);
|
|
43
|
+
|
|
44
|
+
const data = parseJson(result);
|
|
45
|
+
expect(data).toHaveProperty("accounts");
|
|
46
|
+
expect(Array.isArray(data.accounts)).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("create — creates a test spreadsheet", async () => {
|
|
50
|
+
const title = `cliclaw-test-${timestamp}`;
|
|
51
|
+
const result = await run("sheets", "create", "--title", title);
|
|
52
|
+
expect(result.exitCode).toBe(0);
|
|
53
|
+
|
|
54
|
+
const data = parseJson(result);
|
|
55
|
+
expect(data.success).toBe(true);
|
|
56
|
+
expect(data.spreadsheetId).toBeTruthy();
|
|
57
|
+
expect(data.title).toBe(title);
|
|
58
|
+
|
|
59
|
+
spreadsheetId = data.spreadsheetId;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("list — returns spreadsheets including our test spreadsheet", async () => {
|
|
63
|
+
const result = await run("sheets", "list", "--max-results", "10");
|
|
64
|
+
expect(result.exitCode).toBe(0);
|
|
65
|
+
|
|
66
|
+
const files = parseJson(result);
|
|
67
|
+
expect(Array.isArray(files)).toBe(true);
|
|
68
|
+
expect(files.length).toBeGreaterThan(0);
|
|
69
|
+
|
|
70
|
+
const ids = files.map((f: any) => f.id);
|
|
71
|
+
expect(ids).toContain(spreadsheetId);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("get — returns spreadsheet metadata", async () => {
|
|
75
|
+
const result = await run("sheets", "get", "--id", spreadsheetId);
|
|
76
|
+
expect(result.exitCode).toBe(0);
|
|
77
|
+
|
|
78
|
+
const data = parseJson(result);
|
|
79
|
+
expect(data.spreadsheetId).toBe(spreadsheetId);
|
|
80
|
+
expect(data.title).toBe(`cliclaw-test-${timestamp}`);
|
|
81
|
+
expect(Array.isArray(data.sheets)).toBe(true);
|
|
82
|
+
expect(data.sheets.length).toBeGreaterThanOrEqual(1);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("list-sheets — lists sheets/tabs in the spreadsheet", async () => {
|
|
86
|
+
const result = await run("sheets", "list-sheets", "--id", spreadsheetId);
|
|
87
|
+
expect(result.exitCode).toBe(0);
|
|
88
|
+
|
|
89
|
+
const tabs = parseJson(result);
|
|
90
|
+
expect(Array.isArray(tabs)).toBe(true);
|
|
91
|
+
expect(tabs.length).toBeGreaterThanOrEqual(1);
|
|
92
|
+
expect(tabs[0]).toHaveProperty("sheetId");
|
|
93
|
+
expect(tabs[0]).toHaveProperty("title");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("add-sheet — adds a new sheet/tab", async () => {
|
|
97
|
+
const sheetTitle = `TestTab-${timestamp}`;
|
|
98
|
+
const result = await run("sheets", "add-sheet", "--id", spreadsheetId, "--title", sheetTitle);
|
|
99
|
+
expect(result.exitCode).toBe(0);
|
|
100
|
+
|
|
101
|
+
const data = parseJson(result);
|
|
102
|
+
expect(data.success).toBe(true);
|
|
103
|
+
expect(data.title).toBe(sheetTitle);
|
|
104
|
+
expect(data.sheetId).toBeTruthy();
|
|
105
|
+
|
|
106
|
+
addedSheetId = data.sheetId;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("rename-sheet — renames the added sheet", async () => {
|
|
110
|
+
const newTitle = `RenamedTab-${timestamp}`;
|
|
111
|
+
const result = await run(
|
|
112
|
+
"sheets", "rename-sheet",
|
|
113
|
+
"--id", spreadsheetId,
|
|
114
|
+
"--sheet-id", String(addedSheetId),
|
|
115
|
+
"--title", newTitle,
|
|
116
|
+
);
|
|
117
|
+
expect(result.exitCode).toBe(0);
|
|
118
|
+
|
|
119
|
+
const data = parseJson(result);
|
|
120
|
+
expect(data.success).toBe(true);
|
|
121
|
+
expect(data.title).toBe(newTitle);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("write — writes values to cells", async () => {
|
|
125
|
+
const result = await run(
|
|
126
|
+
"sheets", "write",
|
|
127
|
+
"--id", spreadsheetId,
|
|
128
|
+
"--range", "Sheet1!A1:B2",
|
|
129
|
+
"--values", '[["Name","Score"],["Alice","100"]]',
|
|
130
|
+
);
|
|
131
|
+
expect(result.exitCode).toBe(0);
|
|
132
|
+
|
|
133
|
+
const data = parseJson(result);
|
|
134
|
+
expect(data.success).toBe(true);
|
|
135
|
+
expect(data.updatedCells).toBe(4);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("read — reads values back from cells", async () => {
|
|
139
|
+
const result = await run(
|
|
140
|
+
"sheets", "read",
|
|
141
|
+
"--id", spreadsheetId,
|
|
142
|
+
"--range", "Sheet1!A1:B2",
|
|
143
|
+
);
|
|
144
|
+
expect(result.exitCode).toBe(0);
|
|
145
|
+
|
|
146
|
+
const data = parseJson(result);
|
|
147
|
+
expect(data.values).toBeTruthy();
|
|
148
|
+
expect(data.values.length).toBe(2);
|
|
149
|
+
expect(data.values[0]).toEqual(["Name", "Score"]);
|
|
150
|
+
expect(data.values[1]).toEqual(["Alice", "100"]);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("append — appends rows after existing data", async () => {
|
|
154
|
+
const result = await run(
|
|
155
|
+
"sheets", "append",
|
|
156
|
+
"--id", spreadsheetId,
|
|
157
|
+
"--range", "Sheet1!A1:B1",
|
|
158
|
+
"--values", '[["Bob","200"]]',
|
|
159
|
+
);
|
|
160
|
+
expect(result.exitCode).toBe(0);
|
|
161
|
+
|
|
162
|
+
const data = parseJson(result);
|
|
163
|
+
expect(data.success).toBe(true);
|
|
164
|
+
expect(data.updatedRows).toBe(1);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("read — confirms appended data appears", async () => {
|
|
168
|
+
const result = await run(
|
|
169
|
+
"sheets", "read",
|
|
170
|
+
"--id", spreadsheetId,
|
|
171
|
+
"--range", "Sheet1!A3:B3",
|
|
172
|
+
);
|
|
173
|
+
expect(result.exitCode).toBe(0);
|
|
174
|
+
|
|
175
|
+
const data = parseJson(result);
|
|
176
|
+
expect(data.values).toBeTruthy();
|
|
177
|
+
expect(data.values[0]).toEqual(["Bob", "200"]);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("clear — clears a range of cells", async () => {
|
|
181
|
+
const result = await run(
|
|
182
|
+
"sheets", "clear",
|
|
183
|
+
"--id", spreadsheetId,
|
|
184
|
+
"--range", "Sheet1!A1:B3",
|
|
185
|
+
);
|
|
186
|
+
expect(result.exitCode).toBe(0);
|
|
187
|
+
|
|
188
|
+
const data = parseJson(result);
|
|
189
|
+
expect(data.success).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("format — applies bold formatting to a range", async () => {
|
|
193
|
+
// Write some data first
|
|
194
|
+
await run(
|
|
195
|
+
"sheets", "write",
|
|
196
|
+
"--id", spreadsheetId,
|
|
197
|
+
"--range", "Sheet1!A1",
|
|
198
|
+
"--values", '[["Header"]]',
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const result = await run(
|
|
202
|
+
"sheets", "format",
|
|
203
|
+
"--id", spreadsheetId,
|
|
204
|
+
"--range", "A1:A1",
|
|
205
|
+
"--bold",
|
|
206
|
+
);
|
|
207
|
+
expect(result.exitCode).toBe(0);
|
|
208
|
+
|
|
209
|
+
const data = parseJson(result);
|
|
210
|
+
expect(data.success).toBe(true);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("delete-sheet — deletes the added sheet", async () => {
|
|
214
|
+
const result = await run(
|
|
215
|
+
"sheets", "delete-sheet",
|
|
216
|
+
"--id", spreadsheetId,
|
|
217
|
+
"--sheet-id", String(addedSheetId),
|
|
218
|
+
);
|
|
219
|
+
expect(result.exitCode).toBe(0);
|
|
220
|
+
|
|
221
|
+
const data = parseJson(result);
|
|
222
|
+
expect(data.success).toBe(true);
|
|
223
|
+
expect(data.sheetId).toBe(addedSheetId);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("delete — permanently deletes the test spreadsheet", async () => {
|
|
227
|
+
const result = await run("sheets", "delete", "--id", spreadsheetId);
|
|
228
|
+
expect(result.exitCode).toBe(0);
|
|
229
|
+
|
|
230
|
+
const data = parseJson(result);
|
|
231
|
+
expect(data.success).toBe(true);
|
|
232
|
+
expect(data.spreadsheetId).toBe(spreadsheetId);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AgentStore } from "@digitalpresence/cliclaw-auth";
|
|
2
|
+
import { outputJson, outputError } from "../lib/output.js";
|
|
3
|
+
|
|
4
|
+
export async function handleAgentCreate(
|
|
5
|
+
store: AgentStore,
|
|
6
|
+
name: string,
|
|
7
|
+
displayName: string,
|
|
8
|
+
role: string,
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
if (store.get(name)) {
|
|
11
|
+
outputError("agent_exists", `Agent "${name}" already exists`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const now = new Date().toISOString();
|
|
15
|
+
store.scaffoldWorkspace({
|
|
16
|
+
name,
|
|
17
|
+
displayName,
|
|
18
|
+
role,
|
|
19
|
+
permissions: [],
|
|
20
|
+
memory: [],
|
|
21
|
+
cronJobs: [],
|
|
22
|
+
createdAt: now,
|
|
23
|
+
updatedAt: now,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
outputJson({ status: "created", name, displayName });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function handleAgentList(store: AgentStore): Promise<void> {
|
|
30
|
+
const agents = store.list();
|
|
31
|
+
outputJson(
|
|
32
|
+
agents.map((a) => ({
|
|
33
|
+
name: a.name,
|
|
34
|
+
displayName: a.displayName,
|
|
35
|
+
permissions: a.permissions.length,
|
|
36
|
+
memory: a.memory.length,
|
|
37
|
+
})),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function handleAgentShow(store: AgentStore, name: string): Promise<void> {
|
|
42
|
+
const agent = store.get(name);
|
|
43
|
+
if (!agent) {
|
|
44
|
+
outputError("agent_not_found", `Agent "${name}" not found`);
|
|
45
|
+
}
|
|
46
|
+
outputJson(agent);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function handleAgentDelete(store: AgentStore, name: string): Promise<void> {
|
|
50
|
+
if (!store.delete(name)) {
|
|
51
|
+
outputError("agent_not_found", `Agent "${name}" not found`);
|
|
52
|
+
}
|
|
53
|
+
outputJson({ status: "deleted", name });
|
|
54
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { AgentStore } from "@digitalpresence/cliclaw-auth";
|
|
2
|
+
import { outputJson, outputError } from "../lib/output.js";
|
|
3
|
+
|
|
4
|
+
export async function handleAgentMemory(
|
|
5
|
+
store: AgentStore,
|
|
6
|
+
name: string,
|
|
7
|
+
opts?: { tag?: string; recent?: number },
|
|
8
|
+
): Promise<void> {
|
|
9
|
+
const agent = store.get(name);
|
|
10
|
+
if (!agent) {
|
|
11
|
+
outputError("agent_not_found", `Agent "${name}" not found`);
|
|
12
|
+
}
|
|
13
|
+
const memoryStore = store.getMemoryStore(name);
|
|
14
|
+
const entries = memoryStore.list({ tag: opts?.tag, limit: opts?.recent });
|
|
15
|
+
outputJson({ name, count: entries.length, memories: entries });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function handleAgentMemoryAdd(
|
|
19
|
+
store: AgentStore,
|
|
20
|
+
name: string,
|
|
21
|
+
fact: string,
|
|
22
|
+
opts?: { tags?: string; source?: string; importance?: string },
|
|
23
|
+
): Promise<void> {
|
|
24
|
+
const agent = store.get(name);
|
|
25
|
+
if (!agent) {
|
|
26
|
+
outputError("agent_not_found", `Agent "${name}" not found`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const memoryStore = store.getMemoryStore(name);
|
|
30
|
+
const tags = opts?.tags ? opts.tags.split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
31
|
+
const source = (opts?.source as "user" | "agent" | "system") ?? "user";
|
|
32
|
+
const importance = opts?.importance ? (parseInt(opts.importance, 10) as 1 | 2 | 3) : 2;
|
|
33
|
+
|
|
34
|
+
const entry = memoryStore.add(fact, { tags, source, importance });
|
|
35
|
+
store.regenerateClaudeMd(name);
|
|
36
|
+
|
|
37
|
+
outputJson({ status: "added", name, entry });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function handleAgentMemoryRemove(
|
|
41
|
+
store: AgentStore,
|
|
42
|
+
name: string,
|
|
43
|
+
id?: string,
|
|
44
|
+
tag?: string,
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
const agent = store.get(name);
|
|
47
|
+
if (!agent) {
|
|
48
|
+
outputError("agent_not_found", `Agent "${name}" not found`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const memoryStore = store.getMemoryStore(name);
|
|
52
|
+
|
|
53
|
+
if (tag) {
|
|
54
|
+
const removed = memoryStore.removeByTag(tag);
|
|
55
|
+
store.regenerateClaudeMd(name);
|
|
56
|
+
outputJson({ status: "removed", name, removedByTag: tag, count: removed });
|
|
57
|
+
} else if (id) {
|
|
58
|
+
const removed = memoryStore.remove(id);
|
|
59
|
+
if (!removed) {
|
|
60
|
+
outputError("memory_not_found", `Memory "${id}" not found`);
|
|
61
|
+
}
|
|
62
|
+
store.regenerateClaudeMd(name);
|
|
63
|
+
outputJson({ status: "removed", name, id });
|
|
64
|
+
} else {
|
|
65
|
+
outputError("invalid_args", "Provide an ID or --tag to remove");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function handleAgentMemorySearch(
|
|
70
|
+
store: AgentStore,
|
|
71
|
+
name: string,
|
|
72
|
+
query: string,
|
|
73
|
+
): Promise<void> {
|
|
74
|
+
const agent = store.get(name);
|
|
75
|
+
if (!agent) {
|
|
76
|
+
outputError("agent_not_found", `Agent "${name}" not found`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const memoryStore = store.getMemoryStore(name);
|
|
80
|
+
const results = memoryStore.search(query);
|
|
81
|
+
outputJson({ name, query, count: results.length, memories: results });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function handleAgentMemoryClear(store: AgentStore, name: string): Promise<void> {
|
|
85
|
+
const agent = store.get(name);
|
|
86
|
+
if (!agent) {
|
|
87
|
+
outputError("agent_not_found", `Agent "${name}" not found`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const memoryStore = store.getMemoryStore(name);
|
|
91
|
+
const cleared = memoryStore.clear();
|
|
92
|
+
store.regenerateClaudeMd(name);
|
|
93
|
+
|
|
94
|
+
outputJson({ status: "cleared", name, cleared });
|
|
95
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AgentStore } from "@digitalpresence/cliclaw-auth";
|
|
2
|
+
import { outputJson, outputError } from "../lib/output.js";
|
|
3
|
+
|
|
4
|
+
export async function handleAgentGrant(
|
|
5
|
+
store: AgentStore,
|
|
6
|
+
name: string,
|
|
7
|
+
integration: string,
|
|
8
|
+
account: string,
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
const agent = store.get(name);
|
|
11
|
+
if (!agent) {
|
|
12
|
+
outputError("agent_not_found", `Agent "${name}" not found`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const exists = agent.permissions.some(
|
|
16
|
+
(p) => p.integration === integration && p.account === account,
|
|
17
|
+
);
|
|
18
|
+
if (exists) {
|
|
19
|
+
outputError("permission_exists", `Agent "${name}" already has ${integration}:${account}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
agent.permissions.push({ integration, account });
|
|
23
|
+
agent.updatedAt = new Date().toISOString();
|
|
24
|
+
store.save(agent);
|
|
25
|
+
store.regenerateClaudeMd(name);
|
|
26
|
+
|
|
27
|
+
outputJson({ status: "granted", name, integration, account });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function handleAgentRevoke(
|
|
31
|
+
store: AgentStore,
|
|
32
|
+
name: string,
|
|
33
|
+
integration: string,
|
|
34
|
+
account: string,
|
|
35
|
+
): Promise<void> {
|
|
36
|
+
const agent = store.get(name);
|
|
37
|
+
if (!agent) {
|
|
38
|
+
outputError("agent_not_found", `Agent "${name}" not found`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const idx = agent.permissions.findIndex(
|
|
42
|
+
(p) => p.integration === integration && p.account === account,
|
|
43
|
+
);
|
|
44
|
+
if (idx === -1) {
|
|
45
|
+
outputError("permission_not_found", `Agent "${name}" does not have ${integration}:${account}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
agent.permissions.splice(idx, 1);
|
|
49
|
+
agent.updatedAt = new Date().toISOString();
|
|
50
|
+
store.save(agent);
|
|
51
|
+
store.regenerateClaudeMd(name);
|
|
52
|
+
|
|
53
|
+
outputJson({ status: "revoked", name, integration, account });
|
|
54
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import type { OAuthClientManager } from "@digitalpresence/cliclaw-auth";
|
|
3
|
+
import { outputJson } from "../lib/output.js";
|
|
4
|
+
|
|
5
|
+
export async function handleAccounts(clientManager: OAuthClientManager): Promise<void> {
|
|
6
|
+
const allAccounts = clientManager.listAccounts();
|
|
7
|
+
const calendarAccounts = allAccounts.filter((a) => a.startsWith("calendar:"));
|
|
8
|
+
|
|
9
|
+
const accounts = await Promise.all(
|
|
10
|
+
calendarAccounts.map(async (tokenKey) => {
|
|
11
|
+
const account = tokenKey.replace("calendar:", "");
|
|
12
|
+
try {
|
|
13
|
+
const client = clientManager.getClient(tokenKey);
|
|
14
|
+
const calendar = google.calendar({ version: "v3", auth: client });
|
|
15
|
+
const res = await calendar.calendarList.list({ maxResults: 1 });
|
|
16
|
+
const primary = res.data.items?.find((c) => c.primary);
|
|
17
|
+
return { account, email: primary?.id ?? null };
|
|
18
|
+
} catch {
|
|
19
|
+
return { account, email: null };
|
|
20
|
+
}
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
outputJson({ accounts });
|
|
25
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import type { OAuthClientManager } from "@digitalpresence/cliclaw-auth";
|
|
3
|
+
import { waitForOAuthCallback, getCalendarAuthUrl } from "@digitalpresence/cliclaw-auth";
|
|
4
|
+
import { outputJson, outputError } from "../lib/output.js";
|
|
5
|
+
|
|
6
|
+
export async function handleAuth(clientManager: OAuthClientManager, port: number, account: string): Promise<void> {
|
|
7
|
+
const tokenKey = `calendar:${account}`;
|
|
8
|
+
|
|
9
|
+
// Check if already authenticated
|
|
10
|
+
try {
|
|
11
|
+
const client = clientManager.getClient(tokenKey);
|
|
12
|
+
const creds = client.credentials;
|
|
13
|
+
if (creds && (creds.refresh_token || creds.access_token)) {
|
|
14
|
+
const calendar = google.calendar({ version: "v3", auth: client });
|
|
15
|
+
const res = await calendar.calendarList.list({ maxResults: 1 });
|
|
16
|
+
outputJson({
|
|
17
|
+
status: "already_authenticated",
|
|
18
|
+
account,
|
|
19
|
+
message: "Existing session is still valid. No re-authentication needed.",
|
|
20
|
+
});
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
// Tokens invalid — proceed with re-auth
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const open = (await import("open")).default;
|
|
29
|
+
const rawClient = clientManager.getRawClient();
|
|
30
|
+
|
|
31
|
+
const tokens = await waitForOAuthCallback(rawClient, port, (url) => {
|
|
32
|
+
console.error(`Opening browser for Google Calendar authentication...`);
|
|
33
|
+
console.error(url);
|
|
34
|
+
open(url).catch(() => {
|
|
35
|
+
console.error("Could not open browser. Please visit the URL above manually.");
|
|
36
|
+
});
|
|
37
|
+
}, getCalendarAuthUrl);
|
|
38
|
+
|
|
39
|
+
clientManager.setCredentials(tokenKey, tokens);
|
|
40
|
+
|
|
41
|
+
outputJson({ status: "authenticated", account });
|
|
42
|
+
} catch (err) {
|
|
43
|
+
outputError("auth_failed", err instanceof Error ? err.message : String(err));
|
|
44
|
+
}
|
|
45
|
+
}
|