your Mac · your models · your files

She does the work. And it’s all yours.

A personal assistant that lives on your Mac. What she knows and what she makes are files you keep.

One request.

you ask, in plain words

Becomes a result.

she writes a program, runs it, saves the file

Becomes a tool.

an App: one file she built, open in the conversation

Becomes a habit.

a skill: her instructions, in a file you can edit

And it’s all yours.

ordinary files in your Alice folder · quit her and they stay

Which invoices are still unpaid? Total them and save me the list. documents/invoices.json

Two are unpaid: INV-101 Cedar and INV-103 Maple, USD 1875.50 in total. The list is saved.

unpaid106 B

Inspect this run
Program
import { files } from "alice";

export default async function main() {
  const { content } = await files.read({
    path: "documents/invoices.json"
  });
  const invoices = JSON.parse(content);
  const valid = Array.isArray(invoices) &&
    invoices.every((invoice) =>
      invoice.currency === "USD" &&
      ["paid", "unpaid"].includes(invoice.status) &&
      Number.isSafeInteger(invoice.amountCents) &&
      invoice.amountCents >= 0 &&
      /^[A-Z]+-[0-9]+$/.test(invoice.id) &&
      /^[A-Za-z ]+$/.test(invoice.customer)
    );
  if (!valid) throw new Error("Invalid invoice register");

  const unpaid = invoices.filter((invoice) =>
    invoice.status === "unpaid"
  );
  const totalCents = unpaid.reduce(
    (sum, invoice) => sum + invoice.amountCents, 0
  );
  if (!Number.isSafeInteger(totalCents)) {
    throw new Error("Total exceeds safe integer range");
  }
  const usd = (cents) => "USD " + (cents / 100).toFixed(2);
  const rows = unpaid.map((invoice) =>
    "- " + invoice.id + " · " + invoice.customer +
    " · " + usd(invoice.amountCents)
  );
  const report = [
    "# Unpaid invoices", "", ...rows, "",
    "Total: " + usd(totalCents), ""
  ].join("\n");
  const path = "documents/unpaid.md";
  await files.create({ path, content: report });
  return { path, count: unpaid.length, totalCents };
}
Input · documents/invoices.json
[
  {
    "id": "INV-101",
    "customer": "Cedar",
    "status": "unpaid",
    "amountCents": 125000,
    "currency": "USD"
  },
  {
    "id": "INV-102",
    "customer": "Linden",
    "status": "paid",
    "amountCents": 48000,
    "currency": "USD"
  },
  {
    "id": "INV-103",
    "customer": "Maple",
    "status": "unpaid",
    "amountCents": 62550,
    "currency": "USD"
  }
]
Declared capabilities
{
  "purpose": "Read the invoice register and create an unpaid invoice summary",
  "uses": [
    {
      "name": "files.read",
      "scope": {
        "path": "documents/invoices.json"
      }
    },
    {
      "name": "files.create",
      "scope": {
        "path": "documents/unpaid.md"
      }
    }
  ]
}
Saved · documents/unpaid.md
# Unpaid invoices

- INV-101 · Cedar · USD 1250.00
- INV-103 · Maple · USD 625.50

Total: USD 1875.50

This example uses contained JavaScript to read the invoices and save the result. In Alice, your permissions determine which files and tools a run can use. This offline check is not proof of execution inside Alice.

Make that a small app I can open again.

Done. Invoices is open here, and saved as documents/apps/invoices.html.

it works · press Unpaid only

Invoice reminder for Cedar, about INV-101

Hi Dana, I hope the week is treating you well.

A small one from our side: INV-101 for USD 1250.00 is still open. Could you take a look this week?

Thank you, as always.

skills/invoice-reminder/SKILL.mdeditedsaved
---id: invoice-reminderschemaVersion: 1name: Invoice reminderdescription: Reminders for unpaid invoices.---# Invoice reminderWrite to the contact about one unpaid invoice.## Tone## Always- Name the invoice and the amount.- Save a draft. **Never send.**
  • agents/alice.md
    ---id: alicename: Alicemodel: connection: anthropic---You are Alice, a local personal assistant.
  • memories/dana.md
    Dana is the contact at Cedar.She prefers first names and short mail.
  • documents/unpaid.md
    # Unpaid invoices- INV-101 · Cedar · USD 1250.00- INV-103 · Maple · USD 625.50Total: USD 1875.50
  • documents/apps/invoices.html
    <!---id: invoicesname: Invoicesstate: schema_version: 1 rules: - key: { kind: exact, value: register } schema: { type: array, max_items: 50, items: { type: object, max_properties: 4, required: [id, customer, cents, paid], properties: { id: { type: string, max_bytes: 16 }, customer: { type: string, max_bytes: 80 }, cents: { type: integer, minimum: 0, maximum: 100000000 }, paid: { type: boolean } } } } default: [] description: The invoices as Alice last read them from documents/invoices.json. application_behavior: Read on open and drawn. Alice updates it with Values compare-and-swap. - key: { kind: exact, value: view } schema: { type: string, max_bytes: 8 } default: all description: Which invoices show, all or unpaid. application_behavior: Set by the two buttons, so the choice is kept.actions: show: description: Show every invoice, or only the unpaid ones. input: { type: object, properties: { view: { type: string } }, required: [view] } changes: [view]---><div role="group" aria-label="Show"> <button type="button" data-action="show" data-arg-view="all">All</button> <button type="button" data-action="show" data-arg-view="unpaid">Unpaid only</button></div><section id="statement"></section><script>const ns = alice.app.namespace, state = { register: [], view: 'all' };const esc = (s) => String(s).replace(/[&<>"]/g, (c) => '&#' + c.charCodeAt(0) + ';');const usd = (cents) => 'USD ' + (cents / 100).toFixed(2);function draw() { const owed = state.register.filter((one) => !one.paid), shown = state.view === 'unpaid' ? owed : state.register; const rows = shown.map((one) => '<tr><td>' + esc(one.customer) + '</td><td>' + esc(one.id) + '</td><td>' + usd(one.cents) + '</td></tr>'); alice.html('#statement', '<h2>' + usd(owed.reduce((sum, one) => sum + one.cents, 0)) + ' outstanding</h2><table>' + rows.join('') + '</table>');}const read = (key) => alice.call('values.get', { namespace: ns, key }).then((r) => { state[key] = r.value; draw(); });const redraw = ({ value }) => (value ? ((state.register = value), draw()) : read('register'));alice.action('show', async ({ view }) => { state.view = (await alice.update('view', () => view)).value; draw(); });alice.on('values', 'register', redraw);alice.expose({ state: () => state });read('register'); read('view');</script>
  • skills/invoice-reminder/SKILL.md
    ---id: invoice-reminderschemaVersion: 1name: Invoice reminderdescription: Reminders for unpaid invoices.---# Invoice reminderWrite to the contact about one unpaid invoice.## Tone- Be **warm**. Use first names.- Give a line of context before the ask.## Always- Name the invoice and the amount.- Save a draft. **Never send.**
  • threads/…/journal.ndjson
    {"v":1,"seq":1,"id":"…","at":"…","type":"message/user","data":{…}}{"v":1,"seq":2,"id":"…","at":"…","type":"model/request","data":{…}}

Every scene on this page is scripted, not a live session; it makes no model, account, or service calls.

Skip

And it compounds.

A feeds a . A runs it. An holds it all.

four files and one schedulepress a word

packages/invoices.mjs
/*---id: invoices---*/export function unpaid(all) { return all.filter(open);}export function total(all) { return unpaid(all).reduce(add, 0);}const open = (one) => one.status === "unpaid";const add = (sum, one) => sum + one.amountCents;
skills/invoice-reminder/SKILL.md
---id: invoice-reminderschemaVersion: 1name: Invoice reminderdescription: Reminders for unpaid invoices.scripts: [unpaid_report]packages: [invoices]---# Invoice reminderWrite to the contact about one unpaid invoice.## Tone- Be **warm**. Use first names.- Give a line of context before the ask.## Always- Name the invoice and the amount.- Save a draft. **Never send.**
Unpaid invoicesNext 13 Nov 8:00 · Weekdays

Run the unpaid report, then draft the reminders.

Runs as Alice · Europe/Warsaw

Latest run

Unpaid invoices

Scheduled conversation finished.

saved at 8:00 by the scheduled run

A schedule runs her, on your Mac, while it’s awake and Alice is running. A scheduled run asks like any other until you allow it for scheduled runs.

She can change herself.

values.setapp.preferences · appearance.accent: green

Hersettings, agents, skills, packages, schedules and apps are files and values she can edit, under your permission rules.

Signing in, entering keys, picking folders and the Cloud switch stay in your hands. Changing herself gives her no new permissions.

Use a green accent.

Done. Green it is.

Bring what you use.

Your services,her tools.

A remote MCP server by its address, or a web service from its API documentation. By default each call asks first.

“Connect my MCP server at https://mcp.ledger.example/mcp”

Integrations

  • OpenAISigned in with your ChatGPT plan
  • AnthropicKey saved
  • LedgerMCP12 tools · Loaded 10:42
  • MailerWeb service3 operations
  • LM StudioModel serverhttp://127.0.0.1:1234/v1

Say it.Hear it back.

Dictate a message. Have a reply read aloud. Both use an OpenAI or Gemini key.

0:07

Remind Cedar about INV-101, and keep it warm.

dictates with OpenAI · reads aloud with Gemini

Every conversation,found.

History finds a conversation by its title, its agent, or the words in a message.

  • Unpaid invoicesScheduled · today 8:00Working
  • Cedar reminderAlice · today 9:12Needs you
  • Has Maple paid INV-103?Alice · yesterdayUnread
  • Tighten the offerAlice · Monday

On your terms.

Permission,not forgiveness.

By default she asks before she creates, changes or deletes a file.

Draft a payment reminder for Cedar and save it.

documents/replies/cedar-reminder.md
INV-101 for USD 1250.00 is still open, Dana.Could you confirm the payment date?

The modelis a setting.

An API key, your ChatGPT plan, Included AI, or a model server on your Mac.

Answers come through Anthropic, paid with your Anthropic key.

agents/alice.md · model: connection: anthropic

skills · memories · conversations · unchanged

Copy. Press.Paste.

Any skill on a shortcut, in any app.

Pasted

Dana, have you looked at INV-101 yet?

or press ⌃⌥T now

Tighten hey dana did u maybe get a chance to look at inv-101 yet??

Dana, have you looked at INV-101 yet?

Reply copied.

Close the window.She stays.

Work continues.
Questions wait for you.

Has Maple paid INV-103?

I asked Researcher to check the register.

Private by design.Cloud by choice.

Stored on your Mac. Nothing goes to Alice’s servers unless you turn on Cloud.

Account

Cloud

When you use them, online models and connected tools receive the inputs they need. Cloud is in private testing; no date, plan or price is set. Encrypted at rest, not end-to-end: the server holds the key so it can run your work while your Mac is off.

From people who use Alice

Since I started using Alice, it has become an essential tool in my work. It is reliable, and the possibility of integrating it with automations brings a whole new dimension to optimizing processes in the company and increasing personal productivity!
Tom Moskalski CEO www.hugizrecords.com

What wouldyou ask?

Start with one request. See what it becomes.