@rozek/nanoclaw 1.2.17
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/.claude/settings.json +1 -0
- package/.claude/skills/add-compact/SKILL.md +135 -0
- package/.claude/skills/add-discord/SKILL.md +203 -0
- package/.claude/skills/add-gmail/SKILL.md +220 -0
- package/.claude/skills/add-image-vision/SKILL.md +94 -0
- package/.claude/skills/add-ollama-tool/SKILL.md +153 -0
- package/.claude/skills/add-parallel/SKILL.md +290 -0
- package/.claude/skills/add-pdf-reader/SKILL.md +104 -0
- package/.claude/skills/add-reactions/SKILL.md +117 -0
- package/.claude/skills/add-slack/SKILL.md +207 -0
- package/.claude/skills/add-telegram/SKILL.md +222 -0
- package/.claude/skills/add-telegram-swarm/SKILL.md +384 -0
- package/.claude/skills/add-voice-transcription/SKILL.md +148 -0
- package/.claude/skills/add-whatsapp/SKILL.md +372 -0
- package/.claude/skills/convert-to-apple-container/SKILL.md +175 -0
- package/.claude/skills/customize/SKILL.md +110 -0
- package/.claude/skills/debug/SKILL.md +349 -0
- package/.claude/skills/get-qodo-rules/SKILL.md +122 -0
- package/.claude/skills/get-qodo-rules/references/output-format.md +41 -0
- package/.claude/skills/get-qodo-rules/references/pagination.md +33 -0
- package/.claude/skills/get-qodo-rules/references/repository-scope.md +26 -0
- package/.claude/skills/qodo-pr-resolver/SKILL.md +326 -0
- package/.claude/skills/qodo-pr-resolver/resources/providers.md +329 -0
- package/.claude/skills/setup/SKILL.md +218 -0
- package/.claude/skills/update-nanoclaw/SKILL.md +235 -0
- package/.claude/skills/update-skills/SKILL.md +130 -0
- package/.claude/skills/use-local-whisper/SKILL.md +152 -0
- package/.claude/skills/x-integration/SKILL.md +417 -0
- package/.claude/skills/x-integration/agent.ts +243 -0
- package/.claude/skills/x-integration/host.ts +159 -0
- package/.claude/skills/x-integration/lib/browser.ts +148 -0
- package/.claude/skills/x-integration/lib/config.ts +62 -0
- package/.claude/skills/x-integration/scripts/like.ts +56 -0
- package/.claude/skills/x-integration/scripts/post.ts +66 -0
- package/.claude/skills/x-integration/scripts/quote.ts +80 -0
- package/.claude/skills/x-integration/scripts/reply.ts +74 -0
- package/.claude/skills/x-integration/scripts/retweet.ts +62 -0
- package/.claude/skills/x-integration/scripts/setup.ts +87 -0
- package/.env.example +1 -0
- package/.github/CODEOWNERS +10 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +14 -0
- package/.github/workflows/bump-version.yml +32 -0
- package/.github/workflows/ci.yml +25 -0
- package/.github/workflows/merge-forward-skills.yml +160 -0
- package/.github/workflows/update-tokens.yml +42 -0
- package/.husky/pre-commit +1 -0
- package/.mcp.json +3 -0
- package/.nvmrc +1 -0
- package/.prettierrc +3 -0
- package/CHANGELOG.md +8 -0
- package/CLAUDE.md +64 -0
- package/CONTRIBUTING.md +23 -0
- package/CONTRIBUTORS.md +15 -0
- package/LICENSE +21 -0
- package/NanoClaw_with_Web-Support.md +290 -0
- package/README.md +261 -0
- package/README_zh.md +200 -0
- package/assets/nanoclaw-favicon.png +0 -0
- package/assets/nanoclaw-icon.png +0 -0
- package/assets/nanoclaw-logo-dark.png +0 -0
- package/assets/nanoclaw-logo.png +0 -0
- package/assets/nanoclaw-profile.jpeg +0 -0
- package/assets/nanoclaw-sales.png +0 -0
- package/assets/social-preview.jpg +0 -0
- package/config-examples/mount-allowlist.json +25 -0
- package/container/Dockerfile +70 -0
- package/container/agent-runner/package-lock.json +1524 -0
- package/container/agent-runner/package.json +21 -0
- package/container/agent-runner/src/index.ts +558 -0
- package/container/agent-runner/src/ipc-mcp-stdio.ts +338 -0
- package/container/agent-runner/tsconfig.json +15 -0
- package/container/build.sh +23 -0
- package/container/skills/agent-browser/SKILL.md +159 -0
- package/container/skills/capabilities/SKILL.md +100 -0
- package/container/skills/status/SKILL.md +104 -0
- package/dist/channels/index.d.ts +2 -0
- package/dist/channels/index.d.ts.map +1 -0
- package/dist/channels/index.js +9 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/channels/registry.d.ts +13 -0
- package/dist/channels/registry.d.ts.map +1 -0
- package/dist/channels/registry.js +11 -0
- package/dist/channels/registry.js.map +1 -0
- package/dist/channels/registry.test.d.ts +2 -0
- package/dist/channels/registry.test.d.ts.map +1 -0
- package/dist/channels/registry.test.js +32 -0
- package/dist/channels/registry.test.js.map +1 -0
- package/dist/channels/web.d.ts +2 -0
- package/dist/channels/web.d.ts.map +1 -0
- package/dist/channels/web.js +1738 -0
- package/dist/channels/web.js.map +1 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +182 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +36 -0
- package/dist/config.js.map +1 -0
- package/dist/container-runner.d.ts +44 -0
- package/dist/container-runner.d.ts.map +1 -0
- package/dist/container-runner.js +467 -0
- package/dist/container-runner.js.map +1 -0
- package/dist/container-runner.test.d.ts +2 -0
- package/dist/container-runner.test.d.ts.map +1 -0
- package/dist/container-runner.test.js +150 -0
- package/dist/container-runner.test.js.map +1 -0
- package/dist/container-runtime.d.ts +22 -0
- package/dist/container-runtime.d.ts.map +1 -0
- package/dist/container-runtime.js +96 -0
- package/dist/container-runtime.js.map +1 -0
- package/dist/container-runtime.test.d.ts +2 -0
- package/dist/container-runtime.test.d.ts.map +1 -0
- package/dist/container-runtime.test.js +93 -0
- package/dist/container-runtime.test.js.map +1 -0
- package/dist/credential-proxy.d.ts +21 -0
- package/dist/credential-proxy.d.ts.map +1 -0
- package/dist/credential-proxy.js +95 -0
- package/dist/credential-proxy.js.map +1 -0
- package/dist/credential-proxy.test.d.ts +2 -0
- package/dist/credential-proxy.test.d.ts.map +1 -0
- package/dist/credential-proxy.test.js +134 -0
- package/dist/credential-proxy.test.js.map +1 -0
- package/dist/db.d.ts +115 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +549 -0
- package/dist/db.js.map +1 -0
- package/dist/db.test.d.ts +2 -0
- package/dist/db.test.d.ts.map +1 -0
- package/dist/db.test.js +360 -0
- package/dist/db.test.js.map +1 -0
- package/dist/env.d.ts +8 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +42 -0
- package/dist/env.js.map +1 -0
- package/dist/formatting.test.d.ts +2 -0
- package/dist/formatting.test.d.ts.map +1 -0
- package/dist/formatting.test.js +183 -0
- package/dist/formatting.test.js.map +1 -0
- package/dist/group-folder.d.ts +5 -0
- package/dist/group-folder.d.ts.map +1 -0
- package/dist/group-folder.js +44 -0
- package/dist/group-folder.js.map +1 -0
- package/dist/group-folder.test.d.ts +2 -0
- package/dist/group-folder.test.d.ts.map +1 -0
- package/dist/group-folder.test.js +29 -0
- package/dist/group-folder.test.js.map +1 -0
- package/dist/group-queue.d.ts +34 -0
- package/dist/group-queue.d.ts.map +1 -0
- package/dist/group-queue.js +263 -0
- package/dist/group-queue.js.map +1 -0
- package/dist/group-queue.test.d.ts +2 -0
- package/dist/group-queue.test.d.ts.map +1 -0
- package/dist/group-queue.test.js +341 -0
- package/dist/group-queue.test.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +518 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc-auth.test.d.ts +2 -0
- package/dist/ipc-auth.test.d.ts.map +1 -0
- package/dist/ipc-auth.test.js +434 -0
- package/dist/ipc-auth.test.js.map +1 -0
- package/dist/ipc.d.ts +32 -0
- package/dist/ipc.d.ts.map +1 -0
- package/dist/ipc.js +311 -0
- package/dist/ipc.js.map +1 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +14 -0
- package/dist/logger.js.map +1 -0
- package/dist/mount-security.d.ts +34 -0
- package/dist/mount-security.d.ts.map +1 -0
- package/dist/mount-security.js +325 -0
- package/dist/mount-security.js.map +1 -0
- package/dist/remote-control.d.ts +32 -0
- package/dist/remote-control.d.ts.map +1 -0
- package/dist/remote-control.js +185 -0
- package/dist/remote-control.js.map +1 -0
- package/dist/remote-control.test.d.ts +2 -0
- package/dist/remote-control.test.d.ts.map +1 -0
- package/dist/remote-control.test.js +321 -0
- package/dist/remote-control.test.js.map +1 -0
- package/dist/router.d.ts +8 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +37 -0
- package/dist/router.js.map +1 -0
- package/dist/routing.test.d.ts +2 -0
- package/dist/routing.test.d.ts.map +1 -0
- package/dist/routing.test.js +81 -0
- package/dist/routing.test.js.map +1 -0
- package/dist/sender-allowlist.d.ts +14 -0
- package/dist/sender-allowlist.d.ts.map +1 -0
- package/dist/sender-allowlist.js +79 -0
- package/dist/sender-allowlist.js.map +1 -0
- package/dist/sender-allowlist.test.d.ts +2 -0
- package/dist/sender-allowlist.test.d.ts.map +1 -0
- package/dist/sender-allowlist.test.js +186 -0
- package/dist/sender-allowlist.test.js.map +1 -0
- package/dist/session-commands.d.ts +47 -0
- package/dist/session-commands.d.ts.map +1 -0
- package/dist/session-commands.js +102 -0
- package/dist/session-commands.js.map +1 -0
- package/dist/session-commands.test.d.ts +2 -0
- package/dist/session-commands.test.d.ts.map +1 -0
- package/dist/session-commands.test.js +190 -0
- package/dist/session-commands.test.js.map +1 -0
- package/dist/task-scheduler.d.ts +22 -0
- package/dist/task-scheduler.d.ts.map +1 -0
- package/dist/task-scheduler.js +210 -0
- package/dist/task-scheduler.js.map +1 -0
- package/dist/task-scheduler.test.d.ts +2 -0
- package/dist/task-scheduler.test.d.ts.map +1 -0
- package/dist/task-scheduler.test.js +107 -0
- package/dist/task-scheduler.test.js.map +1 -0
- package/dist/timezone.d.ts +6 -0
- package/dist/timezone.d.ts.map +1 -0
- package/dist/timezone.js +17 -0
- package/dist/timezone.js.map +1 -0
- package/dist/timezone.test.d.ts +2 -0
- package/dist/timezone.test.d.ts.map +1 -0
- package/dist/timezone.test.js +23 -0
- package/dist/timezone.test.js.map +1 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/APPLE-CONTAINER-NETWORKING.md +90 -0
- package/docs/DEBUG_CHECKLIST.md +143 -0
- package/docs/REQUIREMENTS.md +196 -0
- package/docs/SDK_DEEP_DIVE.md +643 -0
- package/docs/SECURITY.md +122 -0
- package/docs/SPEC.md +785 -0
- package/docs/docker-sandboxes.md +359 -0
- package/docs/nanoclaw-architecture-final.md +1063 -0
- package/docs/nanorepo-architecture.md +168 -0
- package/docs/skills-as-branches.md +662 -0
- package/groups/global/CLAUDE.md +58 -0
- package/groups/main/CLAUDE.md +246 -0
- package/launchd/com.nanoclaw.plist +32 -0
- package/package.json +45 -0
- package/repo-tokens/README.md +113 -0
- package/repo-tokens/action.yml +186 -0
- package/repo-tokens/badge.svg +23 -0
- package/repo-tokens/examples/green.svg +14 -0
- package/repo-tokens/examples/red.svg +14 -0
- package/repo-tokens/examples/yellow-green.svg +14 -0
- package/repo-tokens/examples/yellow.svg +14 -0
- package/scripts/run-migrations.ts +105 -0
- package/setup/container.ts +144 -0
- package/setup/environment.test.ts +121 -0
- package/setup/environment.ts +94 -0
- package/setup/groups.ts +229 -0
- package/setup/index.ts +58 -0
- package/setup/mounts.ts +115 -0
- package/setup/platform.test.ts +120 -0
- package/setup/platform.ts +132 -0
- package/setup/register.test.ts +257 -0
- package/setup/register.ts +177 -0
- package/setup/service.test.ts +187 -0
- package/setup/service.ts +362 -0
- package/setup/status.ts +16 -0
- package/setup/verify.ts +192 -0
- package/setup.sh +161 -0
- package/src/channels/index.ts +12 -0
- package/src/channels/registry.test.ts +42 -0
- package/src/channels/registry.ts +32 -0
- package/src/channels/web.ts +1856 -0
- package/src/cli.ts +209 -0
- package/src/config.ts +73 -0
- package/src/container-runner.test.ts +210 -0
- package/src/container-runner.ts +707 -0
- package/src/container-runtime.test.ts +149 -0
- package/src/container-runtime.ts +127 -0
- package/src/credential-proxy.test.ts +192 -0
- package/src/credential-proxy.ts +125 -0
- package/src/db.test.ts +484 -0
- package/src/db.ts +803 -0
- package/src/env.ts +42 -0
- package/src/formatting.test.ts +256 -0
- package/src/group-folder.test.ts +43 -0
- package/src/group-folder.ts +44 -0
- package/src/group-queue.test.ts +484 -0
- package/src/group-queue.ts +365 -0
- package/src/index.ts +731 -0
- package/src/ipc-auth.test.ts +679 -0
- package/src/ipc.ts +461 -0
- package/src/logger.ts +16 -0
- package/src/mount-security.ts +419 -0
- package/src/remote-control.test.ts +397 -0
- package/src/remote-control.ts +224 -0
- package/src/router.ts +52 -0
- package/src/routing.test.ts +170 -0
- package/src/sender-allowlist.test.ts +216 -0
- package/src/sender-allowlist.ts +128 -0
- package/src/session-commands.test.ts +247 -0
- package/src/session-commands.ts +163 -0
- package/src/task-scheduler.test.ts +129 -0
- package/src/task-scheduler.ts +295 -0
- package/src/timezone.test.ts +29 -0
- package/src/timezone.ts +16 -0
- package/src/types.ts +107 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +7 -0
- package/vitest.skills.config.ts +7 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* X Integration - Post Tweet
|
|
4
|
+
* Usage: echo '{"content":"Hello world"}' | npx tsx post.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getBrowserContext, runScript, validateContent, config, ScriptResult } from '../lib/browser.js';
|
|
8
|
+
|
|
9
|
+
interface PostInput {
|
|
10
|
+
content: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function postTweet(input: PostInput): Promise<ScriptResult> {
|
|
14
|
+
const { content } = input;
|
|
15
|
+
|
|
16
|
+
const validationError = validateContent(content, 'Tweet');
|
|
17
|
+
if (validationError) return validationError;
|
|
18
|
+
|
|
19
|
+
let context = null;
|
|
20
|
+
try {
|
|
21
|
+
context = await getBrowserContext();
|
|
22
|
+
const page = context.pages()[0] || await context.newPage();
|
|
23
|
+
|
|
24
|
+
await page.goto('https://x.com/home', { timeout: config.timeouts.navigation, waitUntil: 'domcontentloaded' });
|
|
25
|
+
await page.waitForTimeout(config.timeouts.pageLoad);
|
|
26
|
+
|
|
27
|
+
// Check if logged in
|
|
28
|
+
const isLoggedIn = await page.locator('[data-testid="SideNav_AccountSwitcher_Button"]').isVisible().catch(() => false);
|
|
29
|
+
if (!isLoggedIn) {
|
|
30
|
+
const onLoginPage = await page.locator('input[autocomplete="username"]').isVisible().catch(() => false);
|
|
31
|
+
if (onLoginPage) {
|
|
32
|
+
return { success: false, message: 'X login expired. Run /x-integration to re-authenticate.' };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Find and fill tweet input
|
|
37
|
+
const tweetInput = page.locator('[data-testid="tweetTextarea_0"]');
|
|
38
|
+
await tweetInput.waitFor({ timeout: config.timeouts.elementWait * 2 });
|
|
39
|
+
await tweetInput.click();
|
|
40
|
+
await page.waitForTimeout(config.timeouts.afterClick / 2);
|
|
41
|
+
await tweetInput.fill(content);
|
|
42
|
+
await page.waitForTimeout(config.timeouts.afterFill);
|
|
43
|
+
|
|
44
|
+
// Click post button
|
|
45
|
+
const postButton = page.locator('[data-testid="tweetButtonInline"]');
|
|
46
|
+
await postButton.waitFor({ timeout: config.timeouts.elementWait });
|
|
47
|
+
|
|
48
|
+
const isDisabled = await postButton.getAttribute('aria-disabled');
|
|
49
|
+
if (isDisabled === 'true') {
|
|
50
|
+
return { success: false, message: 'Post button disabled. Content may be empty or exceed character limit.' };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await postButton.click();
|
|
54
|
+
await page.waitForTimeout(config.timeouts.afterSubmit);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
success: true,
|
|
58
|
+
message: `Tweet posted: ${content.slice(0, 50)}${content.length > 50 ? '...' : ''}`
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
} finally {
|
|
62
|
+
if (context) await context.close();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
runScript<PostInput>(postTweet);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* X Integration - Quote Tweet
|
|
4
|
+
* Usage: echo '{"tweetUrl":"https://x.com/user/status/123","comment":"My thoughts"}' | npx tsx quote.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getBrowserContext, navigateToTweet, runScript, validateContent, config, ScriptResult } from '../lib/browser.js';
|
|
8
|
+
|
|
9
|
+
interface QuoteInput {
|
|
10
|
+
tweetUrl: string;
|
|
11
|
+
comment: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function quoteTweet(input: QuoteInput): Promise<ScriptResult> {
|
|
15
|
+
const { tweetUrl, comment } = input;
|
|
16
|
+
|
|
17
|
+
if (!tweetUrl) {
|
|
18
|
+
return { success: false, message: 'Please provide a tweet URL' };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const validationError = validateContent(comment, 'Comment');
|
|
22
|
+
if (validationError) return validationError;
|
|
23
|
+
|
|
24
|
+
let context = null;
|
|
25
|
+
try {
|
|
26
|
+
context = await getBrowserContext();
|
|
27
|
+
const { page, success, error } = await navigateToTweet(context, tweetUrl);
|
|
28
|
+
|
|
29
|
+
if (!success) {
|
|
30
|
+
return { success: false, message: error || 'Navigation failed' };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Click retweet button to open menu
|
|
34
|
+
const tweet = page.locator('article[data-testid="tweet"]').first();
|
|
35
|
+
const retweetButton = tweet.locator('[data-testid="retweet"]');
|
|
36
|
+
await retweetButton.waitFor({ timeout: config.timeouts.elementWait });
|
|
37
|
+
await retweetButton.click();
|
|
38
|
+
await page.waitForTimeout(config.timeouts.afterClick);
|
|
39
|
+
|
|
40
|
+
// Click quote option
|
|
41
|
+
const quoteOption = page.getByRole('menuitem').filter({ hasText: /Quote/i });
|
|
42
|
+
await quoteOption.waitFor({ timeout: config.timeouts.elementWait });
|
|
43
|
+
await quoteOption.click();
|
|
44
|
+
await page.waitForTimeout(config.timeouts.afterClick * 1.5);
|
|
45
|
+
|
|
46
|
+
// Find dialog with aria-modal="true"
|
|
47
|
+
const dialog = page.locator('[role="dialog"][aria-modal="true"]');
|
|
48
|
+
await dialog.waitFor({ timeout: config.timeouts.elementWait });
|
|
49
|
+
|
|
50
|
+
// Fill comment
|
|
51
|
+
const quoteInput = dialog.locator('[data-testid="tweetTextarea_0"]');
|
|
52
|
+
await quoteInput.waitFor({ timeout: config.timeouts.elementWait });
|
|
53
|
+
await quoteInput.click();
|
|
54
|
+
await page.waitForTimeout(config.timeouts.afterClick / 2);
|
|
55
|
+
await quoteInput.fill(comment);
|
|
56
|
+
await page.waitForTimeout(config.timeouts.afterFill);
|
|
57
|
+
|
|
58
|
+
// Click submit button
|
|
59
|
+
const submitButton = dialog.locator('[data-testid="tweetButton"]');
|
|
60
|
+
await submitButton.waitFor({ timeout: config.timeouts.elementWait });
|
|
61
|
+
|
|
62
|
+
const isDisabled = await submitButton.getAttribute('aria-disabled');
|
|
63
|
+
if (isDisabled === 'true') {
|
|
64
|
+
return { success: false, message: 'Submit button disabled. Content may be empty or exceed character limit.' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await submitButton.click();
|
|
68
|
+
await page.waitForTimeout(config.timeouts.afterSubmit);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
success: true,
|
|
72
|
+
message: `Quote tweet posted: ${comment.slice(0, 50)}${comment.length > 50 ? '...' : ''}`
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
} finally {
|
|
76
|
+
if (context) await context.close();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
runScript<QuoteInput>(quoteTweet);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* X Integration - Reply to Tweet
|
|
4
|
+
* Usage: echo '{"tweetUrl":"https://x.com/user/status/123","content":"Great post!"}' | npx tsx reply.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getBrowserContext, navigateToTweet, runScript, validateContent, config, ScriptResult } from '../lib/browser.js';
|
|
8
|
+
|
|
9
|
+
interface ReplyInput {
|
|
10
|
+
tweetUrl: string;
|
|
11
|
+
content: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function replyToTweet(input: ReplyInput): Promise<ScriptResult> {
|
|
15
|
+
const { tweetUrl, content } = input;
|
|
16
|
+
|
|
17
|
+
if (!tweetUrl) {
|
|
18
|
+
return { success: false, message: 'Please provide a tweet URL' };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const validationError = validateContent(content, 'Reply');
|
|
22
|
+
if (validationError) return validationError;
|
|
23
|
+
|
|
24
|
+
let context = null;
|
|
25
|
+
try {
|
|
26
|
+
context = await getBrowserContext();
|
|
27
|
+
const { page, success, error } = await navigateToTweet(context, tweetUrl);
|
|
28
|
+
|
|
29
|
+
if (!success) {
|
|
30
|
+
return { success: false, message: error || 'Navigation failed' };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Click reply button
|
|
34
|
+
const tweet = page.locator('article[data-testid="tweet"]').first();
|
|
35
|
+
const replyButton = tweet.locator('[data-testid="reply"]');
|
|
36
|
+
await replyButton.waitFor({ timeout: config.timeouts.elementWait });
|
|
37
|
+
await replyButton.click();
|
|
38
|
+
await page.waitForTimeout(config.timeouts.afterClick * 1.5);
|
|
39
|
+
|
|
40
|
+
// Find dialog with aria-modal="true" to avoid matching other dialogs
|
|
41
|
+
const dialog = page.locator('[role="dialog"][aria-modal="true"]');
|
|
42
|
+
await dialog.waitFor({ timeout: config.timeouts.elementWait });
|
|
43
|
+
|
|
44
|
+
// Fill reply content
|
|
45
|
+
const replyInput = dialog.locator('[data-testid="tweetTextarea_0"]');
|
|
46
|
+
await replyInput.waitFor({ timeout: config.timeouts.elementWait });
|
|
47
|
+
await replyInput.click();
|
|
48
|
+
await page.waitForTimeout(config.timeouts.afterClick / 2);
|
|
49
|
+
await replyInput.fill(content);
|
|
50
|
+
await page.waitForTimeout(config.timeouts.afterFill);
|
|
51
|
+
|
|
52
|
+
// Click submit button
|
|
53
|
+
const submitButton = dialog.locator('[data-testid="tweetButton"]');
|
|
54
|
+
await submitButton.waitFor({ timeout: config.timeouts.elementWait });
|
|
55
|
+
|
|
56
|
+
const isDisabled = await submitButton.getAttribute('aria-disabled');
|
|
57
|
+
if (isDisabled === 'true') {
|
|
58
|
+
return { success: false, message: 'Submit button disabled. Content may be empty or exceed character limit.' };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await submitButton.click();
|
|
62
|
+
await page.waitForTimeout(config.timeouts.afterSubmit);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
success: true,
|
|
66
|
+
message: `Reply posted: ${content.slice(0, 50)}${content.length > 50 ? '...' : ''}`
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
} finally {
|
|
70
|
+
if (context) await context.close();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
runScript<ReplyInput>(replyToTweet);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* X Integration - Retweet
|
|
4
|
+
* Usage: echo '{"tweetUrl":"https://x.com/user/status/123"}' | npx tsx retweet.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getBrowserContext, navigateToTweet, runScript, config, ScriptResult } from '../lib/browser.js';
|
|
8
|
+
|
|
9
|
+
interface RetweetInput {
|
|
10
|
+
tweetUrl: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function retweet(input: RetweetInput): Promise<ScriptResult> {
|
|
14
|
+
const { tweetUrl } = input;
|
|
15
|
+
|
|
16
|
+
if (!tweetUrl) {
|
|
17
|
+
return { success: false, message: 'Please provide a tweet URL' };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let context = null;
|
|
21
|
+
try {
|
|
22
|
+
context = await getBrowserContext();
|
|
23
|
+
const { page, success, error } = await navigateToTweet(context, tweetUrl);
|
|
24
|
+
|
|
25
|
+
if (!success) {
|
|
26
|
+
return { success: false, message: error || 'Navigation failed' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const tweet = page.locator('article[data-testid="tweet"]').first();
|
|
30
|
+
const unretweetButton = tweet.locator('[data-testid="unretweet"]');
|
|
31
|
+
const retweetButton = tweet.locator('[data-testid="retweet"]');
|
|
32
|
+
|
|
33
|
+
// Check if already retweeted
|
|
34
|
+
const alreadyRetweeted = await unretweetButton.isVisible().catch(() => false);
|
|
35
|
+
if (alreadyRetweeted) {
|
|
36
|
+
return { success: true, message: 'Tweet already retweeted' };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
await retweetButton.waitFor({ timeout: config.timeouts.elementWait });
|
|
40
|
+
await retweetButton.click();
|
|
41
|
+
await page.waitForTimeout(config.timeouts.afterClick);
|
|
42
|
+
|
|
43
|
+
// Click retweet confirm option
|
|
44
|
+
const retweetConfirm = page.locator('[data-testid="retweetConfirm"]');
|
|
45
|
+
await retweetConfirm.waitFor({ timeout: config.timeouts.elementWait });
|
|
46
|
+
await retweetConfirm.click();
|
|
47
|
+
await page.waitForTimeout(config.timeouts.afterClick * 2);
|
|
48
|
+
|
|
49
|
+
// Verify
|
|
50
|
+
const nowRetweeted = await unretweetButton.isVisible().catch(() => false);
|
|
51
|
+
if (nowRetweeted) {
|
|
52
|
+
return { success: true, message: 'Retweet successful' };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { success: false, message: 'Retweet action completed but could not verify success' };
|
|
56
|
+
|
|
57
|
+
} finally {
|
|
58
|
+
if (context) await context.close();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
runScript<RetweetInput>(retweet);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* X Integration - Authentication Setup
|
|
4
|
+
* Usage: npx tsx setup.ts
|
|
5
|
+
*
|
|
6
|
+
* Interactive script - opens browser for manual login
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { chromium } from 'playwright';
|
|
10
|
+
import * as readline from 'readline';
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { config, cleanupLockFiles } from '../lib/browser.js';
|
|
14
|
+
|
|
15
|
+
async function setup(): Promise<void> {
|
|
16
|
+
console.log('=== X (Twitter) Authentication Setup ===\n');
|
|
17
|
+
console.log('This will open Chrome for you to log in to X.');
|
|
18
|
+
console.log('Your login session will be saved for automated interactions.\n');
|
|
19
|
+
console.log(`Chrome path: ${config.chromePath}`);
|
|
20
|
+
console.log(`Profile dir: ${config.browserDataDir}\n`);
|
|
21
|
+
|
|
22
|
+
// Ensure directories exist
|
|
23
|
+
fs.mkdirSync(path.dirname(config.authPath), { recursive: true });
|
|
24
|
+
fs.mkdirSync(config.browserDataDir, { recursive: true });
|
|
25
|
+
|
|
26
|
+
cleanupLockFiles();
|
|
27
|
+
|
|
28
|
+
console.log('Launching browser...\n');
|
|
29
|
+
|
|
30
|
+
const context = await chromium.launchPersistentContext(config.browserDataDir, {
|
|
31
|
+
executablePath: config.chromePath,
|
|
32
|
+
headless: false,
|
|
33
|
+
viewport: config.viewport,
|
|
34
|
+
args: config.chromeArgs.slice(0, 3), // Use first 3 args for setup (less restrictive)
|
|
35
|
+
ignoreDefaultArgs: config.chromeIgnoreDefaultArgs,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const page = context.pages()[0] || await context.newPage();
|
|
39
|
+
|
|
40
|
+
// Navigate to login page
|
|
41
|
+
await page.goto('https://x.com/login');
|
|
42
|
+
|
|
43
|
+
console.log('Please log in to X in the browser window.');
|
|
44
|
+
console.log('After you see your home feed, come back here and press Enter.\n');
|
|
45
|
+
|
|
46
|
+
// Wait for user to complete login
|
|
47
|
+
const rl = readline.createInterface({
|
|
48
|
+
input: process.stdin,
|
|
49
|
+
output: process.stdout
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await new Promise<void>(resolve => {
|
|
53
|
+
rl.question('Press Enter when logged in... ', () => {
|
|
54
|
+
rl.close();
|
|
55
|
+
resolve();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Verify login by navigating to home and checking for account button
|
|
60
|
+
console.log('\nVerifying login status...');
|
|
61
|
+
await page.goto('https://x.com/home');
|
|
62
|
+
await page.waitForTimeout(config.timeouts.pageLoad);
|
|
63
|
+
|
|
64
|
+
const isLoggedIn = await page.locator('[data-testid="SideNav_AccountSwitcher_Button"]').isVisible().catch(() => false);
|
|
65
|
+
|
|
66
|
+
if (isLoggedIn) {
|
|
67
|
+
// Save auth marker
|
|
68
|
+
fs.writeFileSync(config.authPath, JSON.stringify({
|
|
69
|
+
authenticated: true,
|
|
70
|
+
timestamp: new Date().toISOString()
|
|
71
|
+
}, null, 2));
|
|
72
|
+
|
|
73
|
+
console.log('\n✅ Authentication successful!');
|
|
74
|
+
console.log(`Session saved to: ${config.browserDataDir}`);
|
|
75
|
+
console.log('\nYou can now use X integration features.');
|
|
76
|
+
} else {
|
|
77
|
+
console.log('\n❌ Could not verify login status.');
|
|
78
|
+
console.log('Please try again and make sure you are logged in to X.');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await context.close();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
setup().catch(err => {
|
|
85
|
+
console.error('Setup failed:', err.message);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
});
|
package/.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Core code - maintainer only
|
|
2
|
+
/src/ @gavrielc @gabi-simons
|
|
3
|
+
/container/ @gavrielc @gabi-simons
|
|
4
|
+
/groups/ @gavrielc @gabi-simons
|
|
5
|
+
/launchd/ @gavrielc @gabi-simons
|
|
6
|
+
/package.json @gavrielc @gabi-simons
|
|
7
|
+
/package-lock.json @gavrielc @gabi-simons
|
|
8
|
+
|
|
9
|
+
# Skills - open to contributors
|
|
10
|
+
/.claude/skills/
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## Type of Change
|
|
2
|
+
|
|
3
|
+
- [ ] **Skill** - adds a new skill in `.claude/skills/`
|
|
4
|
+
- [ ] **Fix** - bug fix or security fix to source code
|
|
5
|
+
- [ ] **Simplification** - reduces or simplifies source code
|
|
6
|
+
|
|
7
|
+
## Description
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## For Skills
|
|
11
|
+
|
|
12
|
+
- [ ] I have not made any changes to source code
|
|
13
|
+
- [ ] My skill contains instructions for Claude to follow (not pre-built code)
|
|
14
|
+
- [ ] I tested this skill on a fresh clone
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Bump version
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths: ['src/**', 'container/**']
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
bump-version:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/create-github-app-token@v1
|
|
13
|
+
id: app-token
|
|
14
|
+
with:
|
|
15
|
+
app-id: ${{ secrets.APP_ID }}
|
|
16
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
17
|
+
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
with:
|
|
20
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
21
|
+
|
|
22
|
+
- name: Bump patch version
|
|
23
|
+
run: |
|
|
24
|
+
npm version patch --no-git-tag-version
|
|
25
|
+
git add package.json package-lock.json
|
|
26
|
+
git diff --cached --quiet && exit 0
|
|
27
|
+
git config user.name "github-actions[bot]"
|
|
28
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
29
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
30
|
+
git commit -m "chore: bump version to $VERSION"
|
|
31
|
+
git pull --rebase
|
|
32
|
+
git push
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
ci:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: actions/setup-node@v4
|
|
13
|
+
with:
|
|
14
|
+
node-version: 20
|
|
15
|
+
cache: npm
|
|
16
|
+
- run: npm ci
|
|
17
|
+
|
|
18
|
+
- name: Format check
|
|
19
|
+
run: npm run format:check
|
|
20
|
+
|
|
21
|
+
- name: Typecheck
|
|
22
|
+
run: npx tsc --noEmit
|
|
23
|
+
|
|
24
|
+
- name: Tests
|
|
25
|
+
run: npx vitest run
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
name: Merge-forward skill branches
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
issues: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
merge-forward:
|
|
13
|
+
if: github.repository == 'qwibitai/nanoclaw'
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
with:
|
|
18
|
+
fetch-depth: 0
|
|
19
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
20
|
+
|
|
21
|
+
- uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: 20
|
|
24
|
+
cache: npm
|
|
25
|
+
|
|
26
|
+
- name: Configure git
|
|
27
|
+
run: |
|
|
28
|
+
git config user.name "github-actions[bot]"
|
|
29
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
30
|
+
|
|
31
|
+
- name: Merge main into each skill branch
|
|
32
|
+
id: merge
|
|
33
|
+
run: |
|
|
34
|
+
FAILED=""
|
|
35
|
+
SUCCEEDED=""
|
|
36
|
+
|
|
37
|
+
# List all remote skill branches
|
|
38
|
+
SKILL_BRANCHES=$(git branch -r --list 'origin/skill/*' | sed 's|origin/||' | xargs)
|
|
39
|
+
|
|
40
|
+
if [ -z "$SKILL_BRANCHES" ]; then
|
|
41
|
+
echo "No skill branches found."
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
for BRANCH in $SKILL_BRANCHES; do
|
|
46
|
+
SKILL_NAME=$(echo "$BRANCH" | sed 's|skill/||')
|
|
47
|
+
echo ""
|
|
48
|
+
echo "=== Processing $BRANCH ==="
|
|
49
|
+
|
|
50
|
+
# Checkout the skill branch
|
|
51
|
+
git checkout -B "$BRANCH" "origin/$BRANCH"
|
|
52
|
+
|
|
53
|
+
# Attempt merge
|
|
54
|
+
if ! git merge main --no-edit; then
|
|
55
|
+
echo "::warning::Merge conflict in $BRANCH"
|
|
56
|
+
git merge --abort
|
|
57
|
+
FAILED="$FAILED $SKILL_NAME"
|
|
58
|
+
continue
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Check if there's anything new to push
|
|
62
|
+
if git diff --quiet "origin/$BRANCH"; then
|
|
63
|
+
echo "$BRANCH is already up to date with main."
|
|
64
|
+
SUCCEEDED="$SUCCEEDED $SKILL_NAME"
|
|
65
|
+
continue
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# Install deps and validate
|
|
69
|
+
npm ci
|
|
70
|
+
|
|
71
|
+
if ! npm run build; then
|
|
72
|
+
echo "::warning::Build failed for $BRANCH"
|
|
73
|
+
git reset --hard "origin/$BRANCH"
|
|
74
|
+
FAILED="$FAILED $SKILL_NAME"
|
|
75
|
+
continue
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
if ! npm test 2>/dev/null; then
|
|
79
|
+
echo "::warning::Tests failed for $BRANCH"
|
|
80
|
+
git reset --hard "origin/$BRANCH"
|
|
81
|
+
FAILED="$FAILED $SKILL_NAME"
|
|
82
|
+
continue
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Push the updated branch
|
|
86
|
+
git push origin "$BRANCH"
|
|
87
|
+
SUCCEEDED="$SUCCEEDED $SKILL_NAME"
|
|
88
|
+
echo "$BRANCH merged and pushed successfully."
|
|
89
|
+
done
|
|
90
|
+
|
|
91
|
+
echo ""
|
|
92
|
+
echo "=== Results ==="
|
|
93
|
+
echo "Succeeded: $SUCCEEDED"
|
|
94
|
+
echo "Failed: $FAILED"
|
|
95
|
+
|
|
96
|
+
# Export for issue creation
|
|
97
|
+
echo "failed=$FAILED" >> "$GITHUB_OUTPUT"
|
|
98
|
+
echo "succeeded=$SUCCEEDED" >> "$GITHUB_OUTPUT"
|
|
99
|
+
|
|
100
|
+
- name: Open issue for failed merges
|
|
101
|
+
if: steps.merge.outputs.failed != ''
|
|
102
|
+
uses: actions/github-script@v7
|
|
103
|
+
with:
|
|
104
|
+
script: |
|
|
105
|
+
const failed = '${{ steps.merge.outputs.failed }}'.trim().split(/\s+/);
|
|
106
|
+
const sha = context.sha.substring(0, 7);
|
|
107
|
+
const body = [
|
|
108
|
+
`The merge-forward workflow failed to merge \`main\` (${sha}) into the following skill branches:`,
|
|
109
|
+
'',
|
|
110
|
+
...failed.map(s => `- \`skill/${s}\`: merge conflict, build failure, or test failure`),
|
|
111
|
+
'',
|
|
112
|
+
'Please resolve manually:',
|
|
113
|
+
'```bash',
|
|
114
|
+
...failed.map(s => [
|
|
115
|
+
`git checkout skill/${s}`,
|
|
116
|
+
`git merge main`,
|
|
117
|
+
`# resolve conflicts, then: git push`,
|
|
118
|
+
''
|
|
119
|
+
]).flat(),
|
|
120
|
+
'```',
|
|
121
|
+
'',
|
|
122
|
+
`Triggered by push to main: ${context.sha}`
|
|
123
|
+
].join('\n');
|
|
124
|
+
|
|
125
|
+
await github.rest.issues.create({
|
|
126
|
+
owner: context.repo.owner,
|
|
127
|
+
repo: context.repo.repo,
|
|
128
|
+
title: `Merge-forward failed for ${failed.length} skill branch(es) after ${sha}`,
|
|
129
|
+
body,
|
|
130
|
+
labels: ['skill-maintenance']
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
- name: Notify channel forks
|
|
134
|
+
if: always()
|
|
135
|
+
uses: actions/github-script@v7
|
|
136
|
+
with:
|
|
137
|
+
github-token: ${{ secrets.FORK_DISPATCH_TOKEN || secrets.GITHUB_TOKEN }}
|
|
138
|
+
script: |
|
|
139
|
+
const forks = [
|
|
140
|
+
'nanoclaw-whatsapp',
|
|
141
|
+
'nanoclaw-telegram',
|
|
142
|
+
'nanoclaw-discord',
|
|
143
|
+
'nanoclaw-slack',
|
|
144
|
+
'nanoclaw-gmail',
|
|
145
|
+
'nanoclaw-docker-sandboxes',
|
|
146
|
+
];
|
|
147
|
+
const sha = context.sha.substring(0, 7);
|
|
148
|
+
for (const repo of forks) {
|
|
149
|
+
try {
|
|
150
|
+
await github.rest.repos.createDispatchEvent({
|
|
151
|
+
owner: 'qwibitai',
|
|
152
|
+
repo,
|
|
153
|
+
event_type: 'upstream-main-updated',
|
|
154
|
+
client_payload: { sha: context.sha },
|
|
155
|
+
});
|
|
156
|
+
console.log(`Notified ${repo}`);
|
|
157
|
+
} catch (e) {
|
|
158
|
+
console.log(`Failed to notify ${repo}: ${e.message}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Update token count
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
paths: ['src/**', 'container/**', 'launchd/**', 'CLAUDE.md']
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
update-tokens:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/create-github-app-token@v1
|
|
14
|
+
id: app-token
|
|
15
|
+
with:
|
|
16
|
+
app-id: ${{ secrets.APP_ID }}
|
|
17
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
18
|
+
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
with:
|
|
21
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
22
|
+
|
|
23
|
+
- uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: '3.12'
|
|
26
|
+
|
|
27
|
+
- uses: ./repo-tokens
|
|
28
|
+
id: tokens
|
|
29
|
+
with:
|
|
30
|
+
include: 'src/**/*.ts container/agent-runner/src/**/*.ts container/Dockerfile container/build.sh launchd/com.nanoclaw.plist CLAUDE.md'
|
|
31
|
+
exclude: 'src/**/*.test.ts'
|
|
32
|
+
badge-path: 'repo-tokens/badge.svg'
|
|
33
|
+
|
|
34
|
+
- name: Commit if changed
|
|
35
|
+
run: |
|
|
36
|
+
git add README.md repo-tokens/badge.svg
|
|
37
|
+
git diff --cached --quiet && exit 0
|
|
38
|
+
git config user.name "github-actions[bot]"
|
|
39
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
40
|
+
git commit -m "docs: update token count to ${{ steps.tokens.outputs.badge }}"
|
|
41
|
+
git pull --rebase
|
|
42
|
+
git push
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npm run format:fix
|
package/.mcp.json
ADDED
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
22
|
package/.prettierrc
ADDED