Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions skills/xmemo/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## [Unreleased]

### Added

- Add `--if-unset` flag to `profile --status later` to prevent re-running sign-in from overwriting an existing user status choice.

### Changed

- Streamline the first-run sign-in flow in SKILL.md to a single canonical prompt followed by non-blocking browser approval and a one-line connection confirmation, silently initializing profile status with `--if-unset` for subsequent recall re-offers.

## 1.1.38

### Changed
Expand Down
30 changes: 12 additions & 18 deletions skills/xmemo/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,25 @@ Give your agent durable memory that survives across sessions, projects, and tool

## First Successful Run

After ClawHub installs this Skill, run these commands from the Skill root to verify connectivity and authenticate:

1. Check public service reachability:
```text
node scripts/xmemo-skill.mjs doctor --anonymous
```
2. For account-backed memory, prefer an `XMEMO_KEY` supplied by a managed
secret store. When a command fails with "No XMemo credential found" (exit code 2), follow First-run sign-in below:
```text
node scripts/xmemo-skill.mjs login --allow-plaintext
```
3. Confirm the credential before running memory operations:
```text
node scripts/xmemo-skill.mjs auth status --verify
```
After ClawHub installs this Skill, follow First-run sign-in below:

### First-run sign-in (keep it short)

When no credential exists, follow this exact sequence:
1. Ask once in the user's language using this canonical first-message template (translate when addressing the user in another language, but keep the template in English): "XMemo is your personal cloud memory — it lets AI remember your projects, preferences, and todos across sessions and tools, so you never have to repeat yourself. Sign in to get started?" Show only this simple introduction and login question first, without doctor, MCP, token, commands, diagnostics, or technical setup.
2. On yes: before invoking login, separately disclose local unencrypted credential storage, and request affirmative confirmation (e.g. "Signing in opens a browser page; afterwards the token is saved unencrypted on this computer. Continue with browser sign-in?"). If declined at either prompt, do not start login or create temporary access; continue the original task. On affirmative confirmation, run `node scripts/xmemo-skill.mjs login --allow-plaintext` and show only the verification URL, the one-time code, and "approve it in your browser" — nothing else.
3. When login finishes: run `node scripts/xmemo-skill.mjs auth status --verify` and tell the user in one line that XMemo is connected. In the same message, if the project does not already contain a `## XMemo memory` section, add one short offer in the user's language (yes / later / don't ask again), e.g. "Want XMemo used automatically in every session of this project? I can add a short XMemo section to AGENTS.md (yes / later / don't ask again)." If the user says yes, follow [references/agent-profile.md](references/agent-profile.md); on later or don't ask again, record the answer with `node scripts/xmemo-skill.mjs profile --status later|never`, then continue the original task.
4. On no: continue the original task without XMemo; do not ask again in the same session unless the user brings it up.
2. On yes: run `node scripts/xmemo-skill.mjs login --allow-plaintext` directly so output is visible while the command is waiting (for example a background task or streaming shell). As soon as the verification URL and one-time code appear, show only those plus "approve it in your browser" — nothing else. Then wait for the command to exit. A short tool timeout must not kill it before approval or expiry.
3. When login succeeds: run `node scripts/xmemo-skill.mjs auth status --verify`, tell the user in one line that XMemo is connected, and continue the original task. Silently run `node scripts/xmemo-skill.mjs profile --status later --if-unset` with no user prompt, so the existing recall re-offer turns on.
4. On no: do not start login or create temporary access; continue the original task without XMemo; do not ask again in the same session unless the user brings it up.
5. Do not explain runtime selection, doctor details, secret-store setup, or other features during sign-in unless the user asks.

### Diagnostics and Advanced Setup

For troubleshooting or non-interactive environments:
- Check public service reachability: `node scripts/xmemo-skill.mjs doctor --anonymous`
- For account-backed memory, prefer an `XMEMO_KEY` supplied by a managed
secret store (see [references/auth-setup.md](references/auth-setup.md)).
- Inspect or verify credentials: `node scripts/xmemo-skill.mjs auth status --verify`

If a command fails, follow its printed next action and read [references/troubleshooting.md](references/troubleshooting.md).

## Runtime Selection
Expand Down
12 changes: 11 additions & 1 deletion skills/xmemo/scripts/commands/profile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,27 @@ export function getProfileInstructions(command = SCRIPT_COMMAND) {

export async function handleProfile(ctx) {
const { flags } = ctx || {};
const ifUnset = Boolean(flags?.['if-unset'] || flags?.if_unset);
if (flags?.status !== undefined) {
const rawStatus = String(flags.status).trim();
if (!STATUSES.includes(rawStatus)) {
console.error(`Invalid status: '${flags.status}'. Allowed values: ${STATUSES.join(', ')}.`);
process.exit(EXIT_CODE.USER_ERROR);
}
recordOfferAnswer(rawStatus);
const result = recordOfferAnswer(rawStatus, { ifUnset });
if (result?.unchanged) {
console.log(`Unchanged: ${result.status}`);
process.exit(EXIT_CODE.SUCCESS);
}
console.log(`Recorded: ${rawStatus}`);
process.exit(EXIT_CODE.SUCCESS);
}

if (ifUnset) {
console.error('--if-unset requires --status.');
process.exit(EXIT_CODE.USER_ERROR);
}

console.log(getProfileInstructions());
process.exit(EXIT_CODE.SUCCESS);
}
6 changes: 5 additions & 1 deletion skills/xmemo/scripts/lib/cli-input.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function parseArgs(args) {
const eq = rawKey.indexOf('=');
const key = eq === -1 ? rawKey : rawKey.slice(0, eq);
const inlineValue = eq === -1 ? undefined : rawKey.slice(eq + 1);
const isBoolFlag = ['json', 'terminal', 'no-json', 'plain', 'verify', 'compact', 'help', 'version', 'allow-plaintext', 'from-stdin', 'anonymous', 'revoke-environment-token', 'expand-documents', 'expand_documents'].includes(key);
const isBoolFlag = ['json', 'terminal', 'no-json', 'plain', 'verify', 'compact', 'help', 'version', 'allow-plaintext', 'from-stdin', 'anonymous', 'revoke-environment-token', 'expand-documents', 'expand_documents', 'if-unset', 'if_unset'].includes(key);
if (isBoolFlag) {
rejectBooleanValue(key, inlineValue);
if (key === 'json') explicitJson = true;
Expand All @@ -69,6 +69,10 @@ export function parseArgs(args) {
flags['expand-documents'] = true;
flags.expand_documents = true;
}
else if (key === 'if-unset' || key === 'if_unset') {
flags['if-unset'] = true;
flags.if_unset = true;
}
else if (key === 'allow-plaintext') options.allowPlaintext = true;
else if (key === 'revoke-environment-token') options.revokeEnvironmentToken = true;
else options[key] = true;
Expand Down
2 changes: 1 addition & 1 deletion skills/xmemo/scripts/lib/core.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export const COMMAND_FLAGS = {
login: new Set(),
register: new Set(['reason']),
logout: new Set(),
profile: new Set(['status']),
profile: new Set(['status', 'if-unset', 'if_unset']),
doctor: new Set(),
read: new Set(['id', 'offset', 'limit', 'bucket', 'scope']),
update: new Set(['id', 'content', 'path', 'metadata', 'bucket', 'scope']),
Expand Down
2 changes: 1 addition & 1 deletion skills/xmemo/scripts/lib/help.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export const COMMAND_USAGE_REGISTRY = {
desc: 'Inspect runtime health, credentials, and connectivity',
},
profile: {
usage: 'profile [--status <later|never>]',
usage: 'profile [--status <later|never>] [--if-unset]',
desc: 'Print recommended instructions for project agent configuration, or record offer answer',
},
};
Expand Down
5 changes: 4 additions & 1 deletion skills/xmemo/scripts/lib/profile-offer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,17 @@ export function writeOfferState(state) {
}
}

export function recordOfferAnswer(status) {
export function recordOfferAnswer(status, options = {}) {
if (!STATUSES.includes(status)) {
const err = new Error(`Invalid status: '${status}'. Allowed values: ${STATUSES.join(', ')}.`);
err.exitCode = 1;
throw err;
}

const state = readOfferState();
if (options?.ifUnset && state.status !== null) {
return { ...state, unchanged: true };
}
if (status === 'later') {
if (state.status !== 'later' || !state.offers) {
state.offers = 1;
Expand Down
Loading
Loading